Skip to content

Commit 17c59b8

Browse files
committed
Add round_ties_even to Float, FloatCore, and Real
1 parent fbdc29a commit 17c59b8

File tree

5 files changed

+317
-1
lines changed

5 files changed

+317
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ jobs:
1212
1.60.0, # MSRV
1313
1.62.0, # has_total_cmp
1414
1.74.0, # has_num_saturating
15+
1.77.0, # has_round_ties_even
1516
stable,
1617
beta,
1718
nightly,

build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ fn main() {
55
ac.emit_expression_cfg("1f64.total_cmp(&2f64)", "has_total_cmp"); // 1.62
66
ac.emit_path_cfg("core::num::Saturating", "has_num_saturating"); // 1.74
77

8+
// round_ties_even is only available in `std`
9+
ac.set_no_std(false);
10+
ac.emit_expression_cfg("1.5f64.round_ties_even()", "has_round_ties_even"); // 1.77
11+
812
autocfg::rerun_path("build.rs");
913
}

ci/rustup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
set -ex
66

77
ci=$(dirname $0)
8-
for version in 1.60.0 1.62.0 1.74.0 stable beta nightly; do
8+
for version in 1.60.0 1.62.0 1.74.0 1.77.0 stable beta nightly; do
99
rustup run "$version" "$ci/test_full.sh"
1010
done

src/float.rs

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,49 @@ pub trait FloatCore: Num + NumCast + Neg<Output = Self> + PartialOrd + Copy {
796796
/// check(f64::NEG_INFINITY, 1 << 52, 972, -1);
797797
/// ```
798798
fn integer_decode(self) -> (u64, i16, i8);
799+
800+
/// Rounds to the nearest integer, with ties biasing towards an even result.
801+
///
802+
/// # Examples
803+
///
804+
/// ```
805+
/// use num_traits::float::FloatCore;
806+
///
807+
/// fn check<T: FloatCore>(x: T, rounded: T) {
808+
/// assert!(x.round_ties_even() == rounded);
809+
/// }
810+
///
811+
/// check(1.0f32, 1.0);
812+
/// check(1.25f32, 1.0);
813+
/// check(1.75f32, 2.0);
814+
/// check(1.5f32, 2.0);
815+
/// check(2.5f32, 2.0);
816+
/// check(3.5f32, 4.0);
817+
/// check(-3.5f32, -4.0);
818+
/// ```
819+
fn round_ties_even(self) -> Self {
820+
let half = (Self::one() + Self::one()).recip();
821+
822+
if self.fract().abs() != half {
823+
self.round()
824+
} else {
825+
let i = self.abs().trunc();
826+
827+
let value = if (i * half).fract() == half {
828+
// -1.5, 1.5, 3.5, ...
829+
self.abs() + half
830+
} else {
831+
// -0.5, 0.5, 2.5, ...
832+
self.abs() - half
833+
};
834+
835+
if self.signum() != value.signum() {
836+
-value
837+
} else {
838+
value
839+
}
840+
}
841+
}
799842
}
800843

801844
impl FloatCore for f32 {
@@ -844,6 +887,11 @@ impl FloatCore for f32 {
844887
Self::powi(self, n: i32) -> Self;
845888
}
846889

890+
#[cfg(all(feature = "std", has_round_ties_even))]
891+
forward! {
892+
Self::round_ties_even(self) -> Self;
893+
}
894+
847895
#[cfg(all(not(feature = "std"), feature = "libm"))]
848896
forward! {
849897
libm::floorf as floor(self) -> Self;
@@ -906,6 +954,11 @@ impl FloatCore for f64 {
906954
Self::powi(self, n: i32) -> Self;
907955
}
908956

957+
#[cfg(all(feature = "std", has_round_ties_even))]
958+
forward! {
959+
Self::round_ties_even(self) -> Self;
960+
}
961+
909962
#[cfg(all(not(feature = "std"), feature = "libm"))]
910963
forward! {
911964
libm::floor as floor(self) -> Self;
@@ -1909,6 +1962,49 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
19091962
self.neg()
19101963
}
19111964
}
1965+
1966+
/// Rounds to the nearest integer, with ties biasing towards an even result.
1967+
///
1968+
/// # Examples
1969+
///
1970+
/// ```
1971+
/// use num_traits::Float;
1972+
///
1973+
/// fn check<T: Float>(x: T, rounded: T) {
1974+
/// assert!(x.round_ties_even() == rounded);
1975+
/// }
1976+
///
1977+
/// check(1.0f32, 1.0);
1978+
/// check(1.25f32, 1.0);
1979+
/// check(1.75f32, 2.0);
1980+
/// check(1.5f32, 2.0);
1981+
/// check(2.5f32, 2.0);
1982+
/// check(3.5f32, 4.0);
1983+
/// check(-3.5f32, -4.0);
1984+
/// ```
1985+
fn round_ties_even(self) -> Self {
1986+
let half = (Self::one() + Self::one()).recip();
1987+
1988+
if self.fract().abs() != half {
1989+
self.round()
1990+
} else {
1991+
let i = self.abs().trunc();
1992+
1993+
let value = if (i * half).fract() == half {
1994+
// -1.5, 1.5, 3.5, ...
1995+
self.abs() + half
1996+
} else {
1997+
// -0.5, 0.5, 2.5, ...
1998+
self.abs() - half
1999+
};
2000+
2001+
if self.signum() != value.signum() {
2002+
-value
2003+
} else {
2004+
value
2005+
}
2006+
}
2007+
}
19122008
}
19132009

19142010
#[cfg(feature = "std")]
@@ -1989,6 +2085,11 @@ macro_rules! float_impl_std {
19892085
Self::atanh(self) -> Self;
19902086
Self::copysign(self, sign: Self) -> Self;
19912087
}
2088+
2089+
#[cfg(has_round_ties_even)]
2090+
forward! {
2091+
Self::round_ties_even(self) -> Self;
2092+
}
19922093
}
19932094
};
19942095
}
@@ -2510,4 +2611,170 @@ mod tests {
25102611
check_lt(f32::INFINITY, f32::NAN);
25112612
check_gt(f32::NAN, 1.0_f32);
25122613
}
2614+
2615+
/// Compares the fallback implementation of [`round_ties_even`] to the one provided by `f32`.`
2616+
///
2617+
/// [`round_ties_even`]: crate::float::FloatCore::round_ties_even
2618+
#[cfg(has_round_ties_even)]
2619+
#[test]
2620+
fn round_ties_even() {
2621+
mod wrapped_f32 {
2622+
use crate::{float::FloatCore, Num, NumCast, One, ToPrimitive, Zero};
2623+
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
2624+
2625+
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
2626+
pub struct WrappedF32(pub f32);
2627+
2628+
impl ToPrimitive for WrappedF32 {
2629+
fn to_i64(&self) -> Option<i64> {
2630+
f32::to_i64(&self.0)
2631+
}
2632+
2633+
fn to_u64(&self) -> Option<u64> {
2634+
f32::to_u64(&self.0)
2635+
}
2636+
}
2637+
2638+
impl NumCast for WrappedF32 {
2639+
fn from<T: crate::ToPrimitive>(n: T) -> Option<Self> {
2640+
Some(Self(<f32 as NumCast>::from(n)?))
2641+
}
2642+
}
2643+
2644+
impl Neg for WrappedF32 {
2645+
type Output = Self;
2646+
2647+
fn neg(self) -> Self::Output {
2648+
Self(self.0.neg())
2649+
}
2650+
}
2651+
2652+
impl Mul for WrappedF32 {
2653+
type Output = Self;
2654+
2655+
fn mul(self, rhs: Self) -> Self::Output {
2656+
Self(f32::mul(self.0, rhs.0))
2657+
}
2658+
}
2659+
2660+
impl Add for WrappedF32 {
2661+
type Output = Self;
2662+
2663+
fn add(self, rhs: Self) -> Self::Output {
2664+
Self(f32::add(self.0, rhs.0))
2665+
}
2666+
}
2667+
2668+
impl Rem for WrappedF32 {
2669+
type Output = Self;
2670+
2671+
fn rem(self, rhs: Self) -> Self::Output {
2672+
Self(f32::rem(self.0, rhs.0))
2673+
}
2674+
}
2675+
2676+
impl Div for WrappedF32 {
2677+
type Output = Self;
2678+
2679+
fn div(self, rhs: Self) -> Self::Output {
2680+
Self(f32::div(self.0, rhs.0))
2681+
}
2682+
}
2683+
2684+
impl Sub for WrappedF32 {
2685+
type Output = Self;
2686+
2687+
fn sub(self, rhs: Self) -> Self::Output {
2688+
Self(f32::sub(self.0, rhs.0))
2689+
}
2690+
}
2691+
2692+
impl One for WrappedF32 {
2693+
fn one() -> Self {
2694+
Self(f32::one())
2695+
}
2696+
}
2697+
2698+
impl Zero for WrappedF32 {
2699+
fn zero() -> Self {
2700+
Self(f32::zero())
2701+
}
2702+
2703+
fn is_zero(&self) -> bool {
2704+
self.0.is_zero()
2705+
}
2706+
}
2707+
2708+
impl Num for WrappedF32 {
2709+
type FromStrRadixErr = <f32 as Num>::FromStrRadixErr;
2710+
2711+
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
2712+
Ok(Self(f32::from_str_radix(str, radix)?))
2713+
}
2714+
}
2715+
2716+
impl FloatCore for WrappedF32 {
2717+
fn infinity() -> Self {
2718+
Self(f32::infinity())
2719+
}
2720+
2721+
fn neg_infinity() -> Self {
2722+
Self(f32::neg_infinity())
2723+
}
2724+
2725+
fn nan() -> Self {
2726+
Self(f32::nan())
2727+
}
2728+
2729+
fn neg_zero() -> Self {
2730+
Self(f32::neg_zero())
2731+
}
2732+
2733+
fn min_value() -> Self {
2734+
Self(f32::min_value())
2735+
}
2736+
2737+
fn min_positive_value() -> Self {
2738+
Self(f32::min_positive_value())
2739+
}
2740+
2741+
fn epsilon() -> Self {
2742+
Self(f32::epsilon())
2743+
}
2744+
2745+
fn max_value() -> Self {
2746+
Self(f32::max_value())
2747+
}
2748+
2749+
fn classify(self) -> core::num::FpCategory {
2750+
f32::classify(self.0)
2751+
}
2752+
2753+
fn to_degrees(self) -> Self {
2754+
Self(f32::to_degrees(self.0))
2755+
}
2756+
2757+
fn to_radians(self) -> Self {
2758+
Self(f32::to_radians(self.0))
2759+
}
2760+
2761+
fn integer_decode(self) -> (u64, i16, i8) {
2762+
f32::integer_decode(self.0)
2763+
}
2764+
}
2765+
}
2766+
2767+
use crate::float::FloatCore;
2768+
use wrapped_f32::WrappedF32;
2769+
2770+
for x in [
2771+
-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,
2772+
2.5, 3.0, 3.5, 4.0, 4.5, 5.0,
2773+
] {
2774+
for dx in -250_000..=250_000 {
2775+
let y = x + (dx as f32 / 1_000_000.0);
2776+
assert_eq!(WrappedF32(y).round_ties_even().0, y.round_ties_even());
2777+
}
2778+
}
2779+
}
25132780
}

src/real.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,49 @@ pub trait Real: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
777777
/// assert!(abs_difference < 1.0e-10);
778778
/// ```
779779
fn atanh(self) -> Self;
780+
781+
/// Rounds to the nearest integer, with ties biasing towards an even result.
782+
///
783+
/// # Examples
784+
///
785+
/// ```
786+
/// use num_traits::real::Real;
787+
///
788+
/// fn check<T: Real>(x: T, rounded: T) {
789+
/// assert!(x.round_ties_even() == rounded);
790+
/// }
791+
///
792+
/// check(1.0f32, 1.0);
793+
/// check(1.25f32, 1.0);
794+
/// check(1.75f32, 2.0);
795+
/// check(1.5f32, 2.0);
796+
/// check(2.5f32, 2.0);
797+
/// check(3.5f32, 4.0);
798+
/// check(-3.5f32, -4.0);
799+
/// ```
800+
fn round_ties_even(self) -> Self {
801+
let half = (Self::one() + Self::one()).recip();
802+
803+
if self.fract().abs() != half {
804+
self.round()
805+
} else {
806+
let i = self.abs().trunc();
807+
808+
let value = if (i * half).fract() == half {
809+
// -1.5, 1.5, 3.5, ...
810+
self.abs() + half
811+
} else {
812+
// -0.5, 0.5, 2.5, ...
813+
self.abs() - half
814+
};
815+
816+
if self.signum() != value.signum() {
817+
-value
818+
} else {
819+
value
820+
}
821+
}
822+
}
780823
}
781824

782825
impl<T: Float> Real for T {
@@ -830,5 +873,6 @@ impl<T: Float> Real for T {
830873
Float::asinh(self) -> Self;
831874
Float::acosh(self) -> Self;
832875
Float::atanh(self) -> Self;
876+
Float::round_ties_even(self) -> Self;
833877
}
834878
}

0 commit comments

Comments
 (0)