Skip to content

Commit dde326c

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

File tree

6 files changed

+285
-1
lines changed

6 files changed

+285
-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: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,29 @@ pub trait FloatCore: Num + NumCast + Neg<Output = Self> + PartialOrd + Copy {
401401
}
402402
}
403403

404+
/// Rounds to the nearest integer, with ties biasing towards an even result.
405+
///
406+
/// # Examples
407+
///
408+
/// ```
409+
/// use num_traits::float::FloatCore;
410+
///
411+
/// fn check<T: FloatCore>(x: T, rounded: T) {
412+
/// assert!(x.round_ties_even() == rounded);
413+
/// }
414+
///
415+
/// check(1.0f32, 1.0);
416+
/// check(1.25f32, 1.0);
417+
/// check(1.75f32, 2.0);
418+
/// check(1.5f32, 2.0);
419+
/// check(2.5f32, 2.0);
420+
/// check(3.5f32, 4.0);
421+
/// check(-3.5f32, -4.0);
422+
/// ```
423+
fn round_ties_even(self) -> Self {
424+
round_ties_even_impl!(self)
425+
}
426+
404427
/// Return the integer part of a number.
405428
///
406429
/// # Examples
@@ -844,6 +867,11 @@ impl FloatCore for f32 {
844867
Self::powi(self, n: i32) -> Self;
845868
}
846869

870+
#[cfg(all(feature = "std", has_round_ties_even))]
871+
forward! {
872+
Self::round_ties_even(self) -> Self;
873+
}
874+
847875
#[cfg(all(not(feature = "std"), feature = "libm"))]
848876
forward! {
849877
libm::floorf as floor(self) -> Self;
@@ -906,6 +934,11 @@ impl FloatCore for f64 {
906934
Self::powi(self, n: i32) -> Self;
907935
}
908936

937+
#[cfg(all(feature = "std", has_round_ties_even))]
938+
forward! {
939+
Self::round_ties_even(self) -> Self;
940+
}
941+
909942
#[cfg(all(not(feature = "std"), feature = "libm"))]
910943
forward! {
911944
libm::floor as floor(self) -> Self;
@@ -1195,6 +1228,29 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
11951228
/// ```
11961229
fn round(self) -> Self;
11971230

1231+
/// Rounds to the nearest integer, with ties biasing towards an even result.
1232+
///
1233+
/// # Examples
1234+
///
1235+
/// ```
1236+
/// use num_traits::Float;
1237+
///
1238+
/// fn check<T: Float>(x: T, rounded: T) {
1239+
/// assert!(x.round_ties_even() == rounded);
1240+
/// }
1241+
///
1242+
/// check(1.0f32, 1.0);
1243+
/// check(1.25f32, 1.0);
1244+
/// check(1.75f32, 2.0);
1245+
/// check(1.5f32, 2.0);
1246+
/// check(2.5f32, 2.0);
1247+
/// check(3.5f32, 4.0);
1248+
/// check(-3.5f32, -4.0);
1249+
/// ```
1250+
fn round_ties_even(self) -> Self {
1251+
round_ties_even_impl!(self)
1252+
}
1253+
11981254
/// Return the integer part of a number.
11991255
///
12001256
/// ```
@@ -1989,6 +2045,11 @@ macro_rules! float_impl_std {
19892045
Self::atanh(self) -> Self;
19902046
Self::copysign(self, sign: Self) -> Self;
19912047
}
2048+
2049+
#[cfg(has_round_ties_even)]
2050+
forward! {
2051+
Self::round_ties_even(self) -> Self;
2052+
}
19922053
}
19932054
};
19942055
}
@@ -2510,4 +2571,170 @@ mod tests {
25102571
check_lt(f32::INFINITY, f32::NAN);
25112572
check_gt(f32::NAN, 1.0_f32);
25122573
}
2574+
2575+
/// Compares the fallback implementation of [`round_ties_even`] to the one provided by `f32`.`
2576+
///
2577+
/// [`round_ties_even`]: crate::float::FloatCore::round_ties_even
2578+
#[cfg(has_round_ties_even)]
2579+
#[test]
2580+
fn round_ties_even() {
2581+
mod wrapped_f32 {
2582+
use crate::{float::FloatCore, Num, NumCast, One, ToPrimitive, Zero};
2583+
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
2584+
2585+
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
2586+
pub struct WrappedF32(pub f32);
2587+
2588+
impl ToPrimitive for WrappedF32 {
2589+
fn to_i64(&self) -> Option<i64> {
2590+
f32::to_i64(&self.0)
2591+
}
2592+
2593+
fn to_u64(&self) -> Option<u64> {
2594+
f32::to_u64(&self.0)
2595+
}
2596+
}
2597+
2598+
impl NumCast for WrappedF32 {
2599+
fn from<T: crate::ToPrimitive>(n: T) -> Option<Self> {
2600+
Some(Self(<f32 as NumCast>::from(n)?))
2601+
}
2602+
}
2603+
2604+
impl Neg for WrappedF32 {
2605+
type Output = Self;
2606+
2607+
fn neg(self) -> Self::Output {
2608+
Self(self.0.neg())
2609+
}
2610+
}
2611+
2612+
impl Mul for WrappedF32 {
2613+
type Output = Self;
2614+
2615+
fn mul(self, rhs: Self) -> Self::Output {
2616+
Self(f32::mul(self.0, rhs.0))
2617+
}
2618+
}
2619+
2620+
impl Add for WrappedF32 {
2621+
type Output = Self;
2622+
2623+
fn add(self, rhs: Self) -> Self::Output {
2624+
Self(f32::add(self.0, rhs.0))
2625+
}
2626+
}
2627+
2628+
impl Rem for WrappedF32 {
2629+
type Output = Self;
2630+
2631+
fn rem(self, rhs: Self) -> Self::Output {
2632+
Self(f32::rem(self.0, rhs.0))
2633+
}
2634+
}
2635+
2636+
impl Div for WrappedF32 {
2637+
type Output = Self;
2638+
2639+
fn div(self, rhs: Self) -> Self::Output {
2640+
Self(f32::div(self.0, rhs.0))
2641+
}
2642+
}
2643+
2644+
impl Sub for WrappedF32 {
2645+
type Output = Self;
2646+
2647+
fn sub(self, rhs: Self) -> Self::Output {
2648+
Self(f32::sub(self.0, rhs.0))
2649+
}
2650+
}
2651+
2652+
impl One for WrappedF32 {
2653+
fn one() -> Self {
2654+
Self(f32::one())
2655+
}
2656+
}
2657+
2658+
impl Zero for WrappedF32 {
2659+
fn zero() -> Self {
2660+
Self(f32::zero())
2661+
}
2662+
2663+
fn is_zero(&self) -> bool {
2664+
self.0.is_zero()
2665+
}
2666+
}
2667+
2668+
impl Num for WrappedF32 {
2669+
type FromStrRadixErr = <f32 as Num>::FromStrRadixErr;
2670+
2671+
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
2672+
Ok(Self(f32::from_str_radix(str, radix)?))
2673+
}
2674+
}
2675+
2676+
impl FloatCore for WrappedF32 {
2677+
fn infinity() -> Self {
2678+
Self(f32::infinity())
2679+
}
2680+
2681+
fn neg_infinity() -> Self {
2682+
Self(f32::neg_infinity())
2683+
}
2684+
2685+
fn nan() -> Self {
2686+
Self(f32::nan())
2687+
}
2688+
2689+
fn neg_zero() -> Self {
2690+
Self(f32::neg_zero())
2691+
}
2692+
2693+
fn min_value() -> Self {
2694+
Self(f32::min_value())
2695+
}
2696+
2697+
fn min_positive_value() -> Self {
2698+
Self(f32::min_positive_value())
2699+
}
2700+
2701+
fn epsilon() -> Self {
2702+
Self(f32::epsilon())
2703+
}
2704+
2705+
fn max_value() -> Self {
2706+
Self(f32::max_value())
2707+
}
2708+
2709+
fn classify(self) -> core::num::FpCategory {
2710+
f32::classify(self.0)
2711+
}
2712+
2713+
fn to_degrees(self) -> Self {
2714+
Self(f32::to_degrees(self.0))
2715+
}
2716+
2717+
fn to_radians(self) -> Self {
2718+
Self(f32::to_radians(self.0))
2719+
}
2720+
2721+
fn integer_decode(self) -> (u64, i16, i8) {
2722+
f32::integer_decode(self.0)
2723+
}
2724+
}
2725+
}
2726+
2727+
use crate::float::FloatCore;
2728+
use wrapped_f32::WrappedF32;
2729+
2730+
for x in [
2731+
-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,
2732+
2.5, 3.0, 3.5, 4.0, 4.5, 5.0,
2733+
] {
2734+
for dx in -250_000..=250_000 {
2735+
let y = x + (dx as f32 / 1_000_000.0);
2736+
assert_eq!(WrappedF32(y).round_ties_even().0, y.round_ties_even());
2737+
}
2738+
}
2739+
}
25132740
}

src/macros.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,31 @@ macro_rules! constant {
4242
}
4343
)*};
4444
}
45+
46+
/// Pulling out the inner implementation of a default `round_ties_even` to allow
47+
/// reuse across the various relevant traits.
48+
macro_rules! round_ties_even_impl {
49+
($self:ident) => {{
50+
let half = (Self::one() + Self::one()).recip();
51+
52+
if $self.fract().abs() != half {
53+
$self.round()
54+
} else {
55+
let i = $self.abs().trunc();
56+
57+
let value = if (i * half).fract() == half {
58+
// -1.5, 1.5, 3.5, ...
59+
$self.abs() + half
60+
} else {
61+
// -0.5, 0.5, 2.5, ...
62+
$self.abs() - half
63+
};
64+
65+
if $self.signum() != value.signum() {
66+
-value
67+
} else {
68+
value
69+
}
70+
}
71+
}};
72+
}

src/real.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,29 @@ pub trait Real: Num + Copy + NumCast + PartialOrd + Neg<Output = Self> {
107107
/// ```
108108
fn round(self) -> Self;
109109

110+
/// Rounds to the nearest integer, with ties biasing towards an even result.
111+
///
112+
/// # Examples
113+
///
114+
/// ```
115+
/// use num_traits::real::Real;
116+
///
117+
/// fn check<T: Real>(x: T, rounded: T) {
118+
/// assert!(x.round_ties_even() == rounded);
119+
/// }
120+
///
121+
/// check(1.0f32, 1.0);
122+
/// check(1.25f32, 1.0);
123+
/// check(1.75f32, 2.0);
124+
/// check(1.5f32, 2.0);
125+
/// check(2.5f32, 2.0);
126+
/// check(3.5f32, 4.0);
127+
/// check(-3.5f32, -4.0);
128+
/// ```
129+
fn round_ties_even(self) -> Self {
130+
round_ties_even_impl!(self)
131+
}
132+
110133
/// Return the integer part of a number.
111134
///
112135
/// ```
@@ -830,5 +853,6 @@ impl<T: Float> Real for T {
830853
Float::asinh(self) -> Self;
831854
Float::acosh(self) -> Self;
832855
Float::atanh(self) -> Self;
856+
Float::round_ties_even(self) -> Self;
833857
}
834858
}

0 commit comments

Comments
 (0)