Skip to content

Commit ab6bfad

Browse files
committed
Add FloatCore::round_ties_even
1 parent 022f250 commit ab6bfad

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ default = ["std"]
2727
libm = ["dep:libm"]
2828
std = []
2929

30+
# Allows access to functionality only available in Rust 1.77, such as round_ties_even
31+
# This increases the MSRV to 1.77
32+
msrv_1_77 = []
33+
3034
# vestigial features, now always in effect
3135
i128 = []
3236

src/float.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,31 @@ 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+
fn round_ties_even(self) -> Self {
802+
let half = (Self::one() + Self::one()).recip();
803+
804+
if self.fract().abs() != half {
805+
self.round()
806+
} else {
807+
let i = self.abs().trunc();
808+
809+
let value = if (i * half).fract() == half {
810+
// -1.5, 1.5, 3.5, ...
811+
self.abs() + half
812+
} else {
813+
// -0.5, 0.5, 2.5, ...
814+
self.abs() - half
815+
};
816+
817+
if self.signum() != value.signum() {
818+
-value
819+
} else {
820+
value
821+
}
822+
}
823+
}
799824
}
800825

801826
impl FloatCore for f32 {
@@ -844,6 +869,11 @@ impl FloatCore for f32 {
844869
Self::powi(self, n: i32) -> Self;
845870
}
846871

872+
#[cfg(all(feature = "std", feature = "msrv_1_77"))]
873+
forward! {
874+
Self::round_ties_even(self) -> Self;
875+
}
876+
847877
#[cfg(all(not(feature = "std"), feature = "libm"))]
848878
forward! {
849879
libm::floorf as floor(self) -> Self;
@@ -906,6 +936,11 @@ impl FloatCore for f64 {
906936
Self::powi(self, n: i32) -> Self;
907937
}
908938

939+
#[cfg(all(feature = "std", feature = "msrv_1_77"))]
940+
forward! {
941+
Self::round_ties_even(self) -> Self;
942+
}
943+
909944
#[cfg(all(not(feature = "std"), feature = "libm"))]
910945
forward! {
911946
libm::floor as floor(self) -> Self;
@@ -2510,4 +2545,170 @@ mod tests {
25102545
check_lt(f32::INFINITY, f32::NAN);
25112546
check_gt(f32::NAN, 1.0_f32);
25122547
}
2548+
2549+
/// Compares the fallback implementation of [`round_ties_even`] to the one provided by `f32`.`
2550+
///
2551+
/// [`round_ties_even`]: crate::float::FloatCore::round_ties_even
2552+
#[cfg(feature = "msrv_1_77")]
2553+
#[test]
2554+
fn round_ties_even() {
2555+
mod wrapped_f32 {
2556+
use crate::{float::FloatCore, Num, NumCast, One, ToPrimitive, Zero};
2557+
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
2558+
2559+
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
2560+
pub struct WrappedF32(pub f32);
2561+
2562+
impl ToPrimitive for WrappedF32 {
2563+
fn to_i64(&self) -> Option<i64> {
2564+
f32::to_i64(&self.0)
2565+
}
2566+
2567+
fn to_u64(&self) -> Option<u64> {
2568+
f32::to_u64(&self.0)
2569+
}
2570+
}
2571+
2572+
impl NumCast for WrappedF32 {
2573+
fn from<T: crate::ToPrimitive>(n: T) -> Option<Self> {
2574+
Some(Self(<f32 as NumCast>::from(n)?))
2575+
}
2576+
}
2577+
2578+
impl Neg for WrappedF32 {
2579+
type Output = Self;
2580+
2581+
fn neg(self) -> Self::Output {
2582+
Self(self.0.neg())
2583+
}
2584+
}
2585+
2586+
impl Mul for WrappedF32 {
2587+
type Output = Self;
2588+
2589+
fn mul(self, rhs: Self) -> Self::Output {
2590+
Self(f32::mul(self.0, rhs.0))
2591+
}
2592+
}
2593+
2594+
impl Add for WrappedF32 {
2595+
type Output = Self;
2596+
2597+
fn add(self, rhs: Self) -> Self::Output {
2598+
Self(f32::add(self.0, rhs.0))
2599+
}
2600+
}
2601+
2602+
impl Rem for WrappedF32 {
2603+
type Output = Self;
2604+
2605+
fn rem(self, rhs: Self) -> Self::Output {
2606+
Self(f32::rem(self.0, rhs.0))
2607+
}
2608+
}
2609+
2610+
impl Div for WrappedF32 {
2611+
type Output = Self;
2612+
2613+
fn div(self, rhs: Self) -> Self::Output {
2614+
Self(f32::div(self.0, rhs.0))
2615+
}
2616+
}
2617+
2618+
impl Sub for WrappedF32 {
2619+
type Output = Self;
2620+
2621+
fn sub(self, rhs: Self) -> Self::Output {
2622+
Self(f32::sub(self.0, rhs.0))
2623+
}
2624+
}
2625+
2626+
impl One for WrappedF32 {
2627+
fn one() -> Self {
2628+
Self(f32::one())
2629+
}
2630+
}
2631+
2632+
impl Zero for WrappedF32 {
2633+
fn zero() -> Self {
2634+
Self(f32::zero())
2635+
}
2636+
2637+
fn is_zero(&self) -> bool {
2638+
self.0.is_zero()
2639+
}
2640+
}
2641+
2642+
impl Num for WrappedF32 {
2643+
type FromStrRadixErr = <f32 as Num>::FromStrRadixErr;
2644+
2645+
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
2646+
Ok(Self(f32::from_str_radix(str, radix)?))
2647+
}
2648+
}
2649+
2650+
impl FloatCore for WrappedF32 {
2651+
fn infinity() -> Self {
2652+
Self(f32::infinity())
2653+
}
2654+
2655+
fn neg_infinity() -> Self {
2656+
Self(f32::neg_infinity())
2657+
}
2658+
2659+
fn nan() -> Self {
2660+
Self(f32::nan())
2661+
}
2662+
2663+
fn neg_zero() -> Self {
2664+
Self(f32::neg_zero())
2665+
}
2666+
2667+
fn min_value() -> Self {
2668+
Self(f32::min_value())
2669+
}
2670+
2671+
fn min_positive_value() -> Self {
2672+
Self(f32::min_positive_value())
2673+
}
2674+
2675+
fn epsilon() -> Self {
2676+
Self(f32::epsilon())
2677+
}
2678+
2679+
fn max_value() -> Self {
2680+
Self(f32::max_value())
2681+
}
2682+
2683+
fn classify(self) -> core::num::FpCategory {
2684+
f32::classify(self.0)
2685+
}
2686+
2687+
fn to_degrees(self) -> Self {
2688+
Self(f32::to_degrees(self.0))
2689+
}
2690+
2691+
fn to_radians(self) -> Self {
2692+
Self(f32::to_radians(self.0))
2693+
}
2694+
2695+
fn integer_decode(self) -> (u64, i16, i8) {
2696+
f32::integer_decode(self.0)
2697+
}
2698+
}
2699+
}
2700+
2701+
use crate::float::FloatCore;
2702+
use wrapped_f32::WrappedF32;
2703+
2704+
for x in [
2705+
-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,
2706+
2.5, 3.0, 3.5, 4.0, 4.5, 5.0,
2707+
] {
2708+
for dx in -250_000..=250_000 {
2709+
let y = x + (dx as f32 / 1_000_000.0);
2710+
assert_eq!(WrappedF32(y).round_ties_even().0, y.round_ties_even());
2711+
}
2712+
}
2713+
}
25132714
}

0 commit comments

Comments
 (0)