Skip to content

Add round_ties_even to Float/Core and Real #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
1.60.0, # MSRV
1.62.0, # has_total_cmp
1.74.0, # has_num_saturating
1.77.0, # has_round_ties_even
stable,
beta,
nightly,
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ fn main() {
ac.emit_expression_cfg("1f64.total_cmp(&2f64)", "has_total_cmp"); // 1.62
ac.emit_path_cfg("core::num::Saturating", "has_num_saturating"); // 1.74

// round_ties_even is only available in `std`
ac.set_no_std(false);
ac.emit_expression_cfg("1.5f64.round_ties_even()", "has_round_ties_even"); // 1.77

autocfg::rerun_path("build.rs");
}
2 changes: 1 addition & 1 deletion ci/rustup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
set -ex

ci=$(dirname $0)
for version in 1.60.0 1.62.0 1.74.0 stable beta nightly; do
for version in 1.60.0 1.62.0 1.74.0 1.77.0 stable beta nightly; do
rustup run "$version" "$ci/test_full.sh"
done
227 changes: 227 additions & 0 deletions src/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,29 @@ pub trait FloatCore: Num + NumCast + Neg<Output = Self> + PartialOrd + Copy {
}
}

/// Rounds to the nearest integer, with ties biasing towards an even result.
///
/// # Examples
///
/// ```
/// use num_traits::float::FloatCore;
///
/// fn check<T: FloatCore>(x: T, rounded: T) {
/// assert!(x.round_ties_even() == rounded);
/// }
///
/// check(1.0f32, 1.0);
/// check(1.25f32, 1.0);
/// check(1.75f32, 2.0);
/// check(1.5f32, 2.0);
/// check(2.5f32, 2.0);
/// check(3.5f32, 4.0);
/// check(-3.5f32, -4.0);
/// ```
fn round_ties_even(self) -> Self {
round_ties_even_impl!(self)
}

/// Return the integer part of a number.
///
/// # Examples
Expand Down Expand Up @@ -844,6 +867,11 @@ impl FloatCore for f32 {
Self::powi(self, n: i32) -> Self;
}

#[cfg(all(feature = "std", has_round_ties_even))]
forward! {
Self::round_ties_even(self) -> Self;
}

#[cfg(all(not(feature = "std"), feature = "libm"))]
forward! {
libm::floorf as floor(self) -> Self;
Expand Down Expand Up @@ -906,6 +934,11 @@ impl FloatCore for f64 {
Self::powi(self, n: i32) -> Self;
}

#[cfg(all(feature = "std", has_round_ties_even))]
forward! {
Self::round_ties_even(self) -> Self;
}

#[cfg(all(not(feature = "std"), feature = "libm"))]
forward! {
libm::floor as floor(self) -> Self;
Expand Down Expand Up @@ -1195,6 +1228,29 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
/// ```
fn round(self) -> Self;

/// Rounds to the nearest integer, with ties biasing towards an even result.
///
/// # Examples
///
/// ```
/// use num_traits::Float;
///
/// fn check<T: Float>(x: T, rounded: T) {
/// assert!(x.round_ties_even() == rounded);
/// }
///
/// check(1.0f32, 1.0);
/// check(1.25f32, 1.0);
/// check(1.75f32, 2.0);
/// check(1.5f32, 2.0);
/// check(2.5f32, 2.0);
/// check(3.5f32, 4.0);
/// check(-3.5f32, -4.0);
/// ```
fn round_ties_even(self) -> Self {
round_ties_even_impl!(self)
}

/// Return the integer part of a number.
///
/// ```
Expand Down Expand Up @@ -1989,6 +2045,11 @@ macro_rules! float_impl_std {
Self::atanh(self) -> Self;
Self::copysign(self, sign: Self) -> Self;
}

#[cfg(has_round_ties_even)]
forward! {
Self::round_ties_even(self) -> Self;
}
}
};
}
Expand Down Expand Up @@ -2510,4 +2571,170 @@ mod tests {
check_lt(f32::INFINITY, f32::NAN);
check_gt(f32::NAN, 1.0_f32);
}

/// Compares the fallback implementation of [`round_ties_even`] to the one provided by `f32`.`
///
/// [`round_ties_even`]: crate::float::FloatCore::round_ties_even
#[cfg(has_round_ties_even)]
#[test]
fn round_ties_even() {
mod wrapped_f32 {
use crate::{float::FloatCore, Num, NumCast, One, ToPrimitive, Zero};
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};

#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct WrappedF32(pub f32);

impl ToPrimitive for WrappedF32 {
fn to_i64(&self) -> Option<i64> {
f32::to_i64(&self.0)
}

fn to_u64(&self) -> Option<u64> {
f32::to_u64(&self.0)
}
}

impl NumCast for WrappedF32 {
fn from<T: crate::ToPrimitive>(n: T) -> Option<Self> {
Some(Self(<f32 as NumCast>::from(n)?))
}
}

impl Neg for WrappedF32 {
type Output = Self;

fn neg(self) -> Self::Output {
Self(self.0.neg())
}
}

impl Mul for WrappedF32 {
type Output = Self;

fn mul(self, rhs: Self) -> Self::Output {
Self(f32::mul(self.0, rhs.0))
}
}

impl Add for WrappedF32 {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
Self(f32::add(self.0, rhs.0))
}
}

impl Rem for WrappedF32 {
type Output = Self;

fn rem(self, rhs: Self) -> Self::Output {
Self(f32::rem(self.0, rhs.0))
}
}

impl Div for WrappedF32 {
type Output = Self;

fn div(self, rhs: Self) -> Self::Output {
Self(f32::div(self.0, rhs.0))
}
}

impl Sub for WrappedF32 {
type Output = Self;

fn sub(self, rhs: Self) -> Self::Output {
Self(f32::sub(self.0, rhs.0))
}
}

impl One for WrappedF32 {
fn one() -> Self {
Self(f32::one())
}
}

impl Zero for WrappedF32 {
fn zero() -> Self {
Self(f32::zero())
}

fn is_zero(&self) -> bool {
self.0.is_zero()
}
}

impl Num for WrappedF32 {
type FromStrRadixErr = <f32 as Num>::FromStrRadixErr;

fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
Ok(Self(f32::from_str_radix(str, radix)?))
}
}

impl FloatCore for WrappedF32 {
fn infinity() -> Self {
Self(f32::infinity())
}

fn neg_infinity() -> Self {
Self(f32::neg_infinity())
}

fn nan() -> Self {
Self(f32::nan())
}

fn neg_zero() -> Self {
Self(f32::neg_zero())
}

fn min_value() -> Self {
Self(f32::min_value())
}

fn min_positive_value() -> Self {
Self(f32::min_positive_value())
}

fn epsilon() -> Self {
Self(f32::epsilon())
}

fn max_value() -> Self {
Self(f32::max_value())
}

fn classify(self) -> core::num::FpCategory {
f32::classify(self.0)
}

fn to_degrees(self) -> Self {
Self(f32::to_degrees(self.0))
}

fn to_radians(self) -> Self {
Self(f32::to_radians(self.0))
}

fn integer_decode(self) -> (u64, i16, i8) {
f32::integer_decode(self.0)
}
}
}

use crate::float::FloatCore;
use wrapped_f32::WrappedF32;

for x in [
-5.0, -4.5, -4.0, -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0,
2.5, 3.0, 3.5, 4.0, 4.5, 5.0,
] {
for dx in -250_000..=250_000 {
let y = x + (dx as f32 / 1_000_000.0);
assert_eq!(WrappedF32(y).round_ties_even().0, y.round_ties_even());
}
}
}
}
28 changes: 28 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,31 @@ macro_rules! constant {
}
)*};
}

/// Pulling out the inner implementation of a default `round_ties_even` to allow
/// reuse across the various relevant traits.
macro_rules! round_ties_even_impl {
($self:ident) => {{
let half = (Self::one() + Self::one()).recip();

if $self.fract().abs() != half {
$self.round()
} else {
let i = $self.abs().trunc();

let value = if (i * half).fract() == half {
// -1.5, 1.5, 3.5, ...
$self.abs() + half
} else {
// -0.5, 0.5, 2.5, ...
$self.abs() - half
};

if $self.signum() != value.signum() {
-value
} else {
value
}
}
}};
}
24 changes: 24 additions & 0 deletions src/real.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,29 @@ pub trait Real: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
/// ```
fn round(self) -> Self;

/// Rounds to the nearest integer, with ties biasing towards an even result.
///
/// # Examples
///
/// ```
/// use num_traits::real::Real;
///
/// fn check<T: Real>(x: T, rounded: T) {
/// assert!(x.round_ties_even() == rounded);
/// }
///
/// check(1.0f32, 1.0);
/// check(1.25f32, 1.0);
/// check(1.75f32, 2.0);
/// check(1.5f32, 2.0);
/// check(2.5f32, 2.0);
/// check(3.5f32, 4.0);
/// check(-3.5f32, -4.0);
/// ```
fn round_ties_even(self) -> Self {
round_ties_even_impl!(self)
}

/// Return the integer part of a number.
///
/// ```
Expand Down Expand Up @@ -830,5 +853,6 @@ impl<T: Float> Real for T {
Float::asinh(self) -> Self;
Float::acosh(self) -> Self;
Float::atanh(self) -> Self;
Float::round_ties_even(self) -> Self;
}
}