Add a generic version of rint

Use this to implement `rint` and `rintf`.
This commit is contained in:
Trevor Gross 2025-01-22 09:06:26 +00:00
parent 3a96a55e70
commit bf2d171b1a
5 changed files with 78 additions and 94 deletions

View file

@ -654,6 +654,7 @@
"src/libm_helper.rs",
"src/math/arch/aarch64.rs",
"src/math/arch/wasm32.rs",
"src/math/generic/rint.rs",
"src/math/rint.rs"
],
"type": "f64"
@ -662,6 +663,7 @@
"sources": [
"src/math/arch/aarch64.rs",
"src/math/arch/wasm32.rs",
"src/math/generic/rint.rs",
"src/math/rintf.rs"
],
"type": "f32"

View file

@ -3,6 +3,7 @@ mod copysign;
mod fabs;
mod fdim;
mod floor;
mod rint;
mod sqrt;
mod trunc;
@ -11,5 +12,6 @@ pub use copysign::copysign;
pub use fabs::fabs;
pub use fdim::fdim;
pub use floor::floor;
pub use rint::rint;
pub use sqrt::sqrt;
pub use trunc::trunc;

View file

@ -0,0 +1,72 @@
/* SPDX-License-Identifier: MIT */
/* origin: musl src/math/rint.c */
use super::super::Float;
pub fn rint<F: Float>(x: F) -> F {
let toint = F::ONE / F::EPSILON;
let e = x.exp();
let positive = x.is_sign_positive();
// On i386 `force_eval!` must be used to force rounding via storage to memory. Otherwise,
// the excess precission from x87 would cause an incorrect final result.
let use_force = cfg!(x86_no_sse) && F::BITS == 32 || F::BITS == 64;
if e >= F::EXP_BIAS + F::SIG_BITS {
// No fractional part; exact result can be returned.
x
} else {
// Apply a net-zero adjustment that nudges `y` in the direction of the rounding mode.
let y = if positive {
let tmp = if use_force { force_eval!(x) } else { x } + toint;
(if use_force { force_eval!(tmp) } else { tmp } - toint)
} else {
let tmp = if use_force { force_eval!(x) } else { x } - toint;
(if use_force { force_eval!(tmp) } else { tmp } + toint)
};
if y == F::ZERO {
// A zero result takes the sign of the input.
if positive { F::ZERO } else { F::NEG_ZERO }
} else {
y
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zeroes_f32() {
assert_biteq!(rint(0.0_f32), 0.0_f32);
assert_biteq!(rint(-0.0_f32), -0.0_f32);
}
#[test]
fn sanity_check_f32() {
assert_biteq!(rint(-1.0_f32), -1.0);
assert_biteq!(rint(2.8_f32), 3.0);
assert_biteq!(rint(-0.5_f32), -0.0);
assert_biteq!(rint(0.5_f32), 0.0);
assert_biteq!(rint(-1.5_f32), -2.0);
assert_biteq!(rint(1.5_f32), 2.0);
}
#[test]
fn zeroes_f64() {
assert_biteq!(rint(0.0_f64), 0.0_f64);
assert_biteq!(rint(-0.0_f64), -0.0_f64);
}
#[test]
fn sanity_check_f64() {
assert_biteq!(rint(-1.0_f64), -1.0);
assert_biteq!(rint(2.8_f64), 3.0);
assert_biteq!(rint(-0.5_f64), -0.0);
assert_biteq!(rint(0.5_f64), 0.0);
assert_biteq!(rint(-1.5_f64), -2.0);
assert_biteq!(rint(1.5_f64), 2.0);
}
}

View file

@ -9,51 +9,5 @@ pub fn rint(x: f64) -> f64 {
args: x,
}
let one_over_e = 1.0 / f64::EPSILON;
let as_u64: u64 = x.to_bits();
let exponent: u64 = (as_u64 >> 52) & 0x7ff;
let is_positive = (as_u64 >> 63) == 0;
if exponent >= 0x3ff + 52 {
x
} else {
let ans = if is_positive {
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let x = force_eval!(x);
let xplusoneovere = x + one_over_e;
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let xplusoneovere = force_eval!(xplusoneovere);
xplusoneovere - one_over_e
} else {
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let x = force_eval!(x);
let xminusoneovere = x - one_over_e;
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let xminusoneovere = force_eval!(xminusoneovere);
xminusoneovere + one_over_e
};
if ans == 0.0 { if is_positive { 0.0 } else { -0.0 } } else { ans }
}
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
#[cfg(test)]
mod tests {
use super::rint;
#[test]
fn negative_zero() {
assert_eq!(rint(-0.0_f64).to_bits(), (-0.0_f64).to_bits());
}
#[test]
fn sanity_check() {
assert_eq!(rint(-1.0), -1.0);
assert_eq!(rint(2.8), 3.0);
assert_eq!(rint(-0.5), -0.0);
assert_eq!(rint(0.5), 0.0);
assert_eq!(rint(-1.5), -2.0);
assert_eq!(rint(1.5), 2.0);
}
super::generic::rint(x)
}

View file

@ -9,51 +9,5 @@ pub fn rintf(x: f32) -> f32 {
args: x,
}
let one_over_e = 1.0 / f32::EPSILON;
let as_u32: u32 = x.to_bits();
let exponent: u32 = (as_u32 >> 23) & 0xff;
let is_positive = (as_u32 >> 31) == 0;
if exponent >= 0x7f + 23 {
x
} else {
let ans = if is_positive {
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let x = force_eval!(x);
let xplusoneovere = x + one_over_e;
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let xplusoneovere = force_eval!(xplusoneovere);
xplusoneovere - one_over_e
} else {
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let x = force_eval!(x);
let xminusoneovere = x - one_over_e;
#[cfg(all(target_arch = "x86", not(target_feature = "sse2")))]
let xminusoneovere = force_eval!(xminusoneovere);
xminusoneovere + one_over_e
};
if ans == 0.0 { if is_positive { 0.0 } else { -0.0 } } else { ans }
}
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
#[cfg(test)]
mod tests {
use super::rintf;
#[test]
fn negative_zero() {
assert_eq!(rintf(-0.0_f32).to_bits(), (-0.0_f32).to_bits());
}
#[test]
fn sanity_check() {
assert_eq!(rintf(-1.0), -1.0);
assert_eq!(rintf(2.8), 3.0);
assert_eq!(rintf(-0.5), -0.0);
assert_eq!(rintf(0.5), 0.0);
assert_eq!(rintf(-1.5), -2.0);
assert_eq!(rintf(1.5), 2.0);
}
super::generic::rint(x)
}