Add a generic version of trunc

The algorithm is identical for both types, so this is a straightforward
routine to port.
This commit is contained in:
Trevor Gross 2025-01-11 23:38:19 +00:00
parent dfa694a8e4
commit 6ac06a97e5
5 changed files with 59 additions and 51 deletions

View file

@ -1,5 +1,7 @@
mod copysign;
mod fabs;
mod trunc;
pub use copysign::copysign;
pub use fabs::fabs;
pub use trunc::trunc;

View file

@ -0,0 +1,54 @@
use super::super::{Float, Int, IntTy, MinInt};
pub fn trunc<F: Float>(x: F) -> F {
let mut xi: F::Int = x.to_bits();
let e: i32 = x.exp_unbiased();
// C1: The represented value has no fractional part, so no truncation is needed
if e >= F::SIG_BITS as i32 {
return x;
}
let mask = if e < 0 {
// C2: If the exponent is negative, the result will be zero so we mask out everything
// except the sign.
F::SIGN_MASK
} else {
// C3: Otherwise, we mask out the last `e` bits of the significand.
!(F::SIG_MASK >> e.unsigned())
};
// C4: If the to-be-masked-out portion is already zero, we have an exact result
if (xi & !mask) == IntTy::<F>::ZERO {
return x;
}
// C5: Otherwise the result is inexact and we will truncate. Raise `FE_INEXACT`, mask the
// result, and return.
force_eval!(x + F::MAX);
xi &= mask;
F::from_bits(xi)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanity_check() {
assert_biteq!(trunc(1.1f32), 1.0);
assert_biteq!(trunc(1.1f64), 1.0);
// C1
assert_biteq!(trunc(hf32!("0x1p23")), hf32!("0x1p23"));
assert_biteq!(trunc(hf64!("0x1p52")), hf64!("0x1p52"));
assert_biteq!(trunc(hf32!("-0x1p23")), hf32!("-0x1p23"));
assert_biteq!(trunc(hf64!("-0x1p52")), hf64!("-0x1p52"));
// C2
assert_biteq!(trunc(hf32!("0x1p-1")), 0.0);
assert_biteq!(trunc(hf64!("0x1p-1")), 0.0);
assert_biteq!(trunc(hf32!("-0x1p-1")), -0.0);
assert_biteq!(trunc(hf64!("-0x1p-1")), -0.0);
}
}

View file

@ -121,7 +121,7 @@ use self::rem_pio2::rem_pio2;
use self::rem_pio2_large::rem_pio2_large;
use self::rem_pio2f::rem_pio2f;
#[allow(unused_imports)]
use self::support::{CastFrom, CastInto, DInt, Float, HInt, Int, MinInt};
use self::support::{CastFrom, CastInto, DInt, Float, HInt, Int, IntTy, MinInt};
// Public modules
mod acos;

View file

@ -1,5 +1,3 @@
use core::f64;
/// Rounds the number toward 0 to the closest integral value (f64).
///
/// This effectively removes the decimal part of the number, leaving the integral part.
@ -11,31 +9,5 @@ pub fn trunc(x: f64) -> f64 {
args: x,
}
let x1p120 = f64::from_bits(0x4770000000000000); // 0x1p120f === 2 ^ 120
let mut i: u64 = x.to_bits();
let mut e: i64 = ((i >> 52) & 0x7ff) as i64 - 0x3ff + 12;
let m: u64;
if e >= 52 + 12 {
return x;
}
if e < 12 {
e = 1;
}
m = -1i64 as u64 >> e;
if (i & m) == 0 {
return x;
}
force_eval!(x + x1p120);
i &= !m;
f64::from_bits(i)
}
#[cfg(test)]
mod tests {
#[test]
fn sanity_check() {
assert_eq!(super::trunc(1.1), 1.0);
}
super::generic::trunc(x)
}

View file

@ -1,5 +1,3 @@
use core::f32;
/// Rounds the number toward 0 to the closest integral value (f32).
///
/// This effectively removes the decimal part of the number, leaving the integral part.
@ -11,25 +9,7 @@ pub fn truncf(x: f32) -> f32 {
args: x,
}
let x1p120 = f32::from_bits(0x7b800000); // 0x1p120f === 2 ^ 120
let mut i: u32 = x.to_bits();
let mut e: i32 = ((i >> 23) & 0xff) as i32 - 0x7f + 9;
let m: u32;
if e >= 23 + 9 {
return x;
}
if e < 9 {
e = 1;
}
m = -1i32 as u32 >> e;
if (i & m) == 0 {
return x;
}
force_eval!(x + x1p120);
i &= !m;
f32::from_bits(i)
super::generic::trunc(x)
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520