diff --git a/crates/compiler-builtins-smoke-test/src/lib.rs b/crates/compiler-builtins-smoke-test/src/lib.rs index 95ecb840d..3416a2229 100644 --- a/crates/compiler-builtins-smoke-test/src/lib.rs +++ b/crates/compiler-builtins-smoke-test/src/lib.rs @@ -157,6 +157,8 @@ no_mangle! { tgammaf(x: f32) -> f32; trunc(x: f64) -> f64; truncf(x: f32) -> f32; + truncf128(x: f128) -> f128; + truncf16(x: f16) -> f16; y0(x: f64) -> f64; y0f(x: f32) -> f32; y1(x: f64) -> f64; diff --git a/crates/libm-macros/src/shared.rs b/crates/libm-macros/src/shared.rs index 16547404f..24fccd6f2 100644 --- a/crates/libm-macros/src/shared.rs +++ b/crates/libm-macros/src/shared.rs @@ -9,7 +9,7 @@ const ALL_OPERATIONS_NESTED: &[(FloatTy, Signature, Option, &[&str])] FloatTy::F16, Signature { args: &[Ty::F16], returns: &[Ty::F16] }, None, - &["fabsf16"], + &["fabsf16", "truncf16"], ), ( // `fn(f32) -> f32` @@ -40,7 +40,7 @@ const ALL_OPERATIONS_NESTED: &[(FloatTy, Signature, Option, &[&str])] FloatTy::F128, Signature { args: &[Ty::F128], returns: &[Ty::F128] }, None, - &["fabsf128"], + &["fabsf128", "truncf128"], ), ( // `(f16, f16) -> f16` diff --git a/crates/libm-test/benches/random.rs b/crates/libm-test/benches/random.rs index cd1e2d2cc..8c6afff25 100644 --- a/crates/libm-test/benches/random.rs +++ b/crates/libm-test/benches/random.rs @@ -117,7 +117,7 @@ libm_macros::for_each_function! { exp10 | exp10f | exp2 | exp2f => (true, Some(musl_math_sys::MACRO_FN_NAME)), // Musl does not provide `f16` and `f128` functions - copysignf16 | copysignf128 | fabsf16 | fabsf128 => (false, None), + copysignf16 | copysignf128 | fabsf16 | fabsf128 | truncf16 | truncf128 => (false, None), // By default we never skip (false) and always have a musl function available _ => (false, Some(musl_math_sys::MACRO_FN_NAME)) diff --git a/crates/libm-test/src/domain.rs b/crates/libm-test/src/domain.rs index 52393d402..adafb9faa 100644 --- a/crates/libm-test/src/domain.rs +++ b/crates/libm-test/src/domain.rs @@ -199,3 +199,13 @@ impl HasDomain for crate::op::fabsf16::Routine { impl HasDomain for crate::op::fabsf128::Routine { const DOMAIN: Domain = Domain::::UNBOUNDED; } + +#[cfg(f16_enabled)] +impl HasDomain for crate::op::truncf16::Routine { + const DOMAIN: Domain = Domain::::UNBOUNDED; +} + +#[cfg(f128_enabled)] +impl HasDomain for crate::op::truncf128::Routine { + const DOMAIN: Domain = Domain::::UNBOUNDED; +} diff --git a/crates/libm-test/src/mpfloat.rs b/crates/libm-test/src/mpfloat.rs index 092f5f1d2..2a740ed47 100644 --- a/crates/libm-test/src/mpfloat.rs +++ b/crates/libm-test/src/mpfloat.rs @@ -141,6 +141,7 @@ libm_macros::for_each_function! { lgamma_r, lgammaf_r, modf, modff, nextafter, nextafterf, pow,powf, remquo, remquof, scalbn, scalbnf, sincos, sincosf, yn, ynf, copysignf16, copysignf128, fabsf16, fabsf128, + truncf16, truncf128, ], fn_extra: match MACRO_FN_NAME { // Remap function names that are different between mpfr and libm @@ -202,11 +203,13 @@ impl_no_round! { #[cfg(f16_enabled)] impl_no_round! { fabsf16 => abs_mut; + truncf16 => trunc_mut; } #[cfg(f128_enabled)] impl_no_round! { fabsf128 => abs_mut; + truncf128 => trunc_mut; } /// Some functions are difficult to do in a generic way. Implement them here. diff --git a/crates/libm-test/tests/compare_built_musl.rs b/crates/libm-test/tests/compare_built_musl.rs index b91d7f9f5..a395c6c5d 100644 --- a/crates/libm-test/tests/compare_built_musl.rs +++ b/crates/libm-test/tests/compare_built_musl.rs @@ -48,7 +48,7 @@ where libm_macros::for_each_function! { callback: musl_rand_tests, // Musl does not support `f16` and `f128` on all platforms. - skip: [copysignf16, copysignf128, fabsf16, fabsf128], + skip: [copysignf16, copysignf128, fabsf16, fabsf128, truncf16, truncf128], attributes: [ #[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586 [exp10, exp10f, exp2, exp2f, rint] @@ -146,5 +146,7 @@ libm_macros::for_each_function! { // Not provided by musl fabsf16, fabsf128, + truncf16, + truncf128, ], } diff --git a/crates/util/src/main.rs b/crates/util/src/main.rs index f7bd31bb6..c8a03068a 100644 --- a/crates/util/src/main.rs +++ b/crates/util/src/main.rs @@ -84,7 +84,7 @@ fn do_eval(basis: &str, op: &str, inputs: &[&str]) { emit_types: [CFn, RustFn, RustArgs], extra: (basis, op, inputs), fn_extra: match MACRO_FN_NAME { - copysignf16 | copysignf128 | fabsf16 | fabsf128 => None, + copysignf16 | copysignf128 | fabsf16 | fabsf128 | truncf16 | truncf128 => None, _ => Some(musl_math_sys::MACRO_FN_NAME) } } diff --git a/etc/function-definitions.json b/etc/function-definitions.json index 39b6c9702..86fa02101 100644 --- a/etc/function-definitions.json +++ b/etc/function-definitions.json @@ -743,6 +743,7 @@ "sources": [ "src/libm_helper.rs", "src/math/arch/wasm32.rs", + "src/math/generic/trunc.rs", "src/math/trunc.rs" ], "type": "f64" @@ -750,10 +751,25 @@ "truncf": { "sources": [ "src/math/arch/wasm32.rs", + "src/math/generic/trunc.rs", "src/math/truncf.rs" ], "type": "f32" }, + "truncf128": { + "sources": [ + "src/math/generic/trunc.rs", + "src/math/truncf128.rs" + ], + "type": "f128" + }, + "truncf16": { + "sources": [ + "src/math/generic/trunc.rs", + "src/math/truncf16.rs" + ], + "type": "f16" + }, "y0": { "sources": [ "src/libm_helper.rs", diff --git a/etc/function-list.txt b/etc/function-list.txt index 0a1bbab24..8aa901762 100644 --- a/etc/function-list.txt +++ b/etc/function-list.txt @@ -111,6 +111,8 @@ tgamma tgammaf trunc truncf +truncf128 +truncf16 y0 y0f y1 diff --git a/src/math/generic/mod.rs b/src/math/generic/mod.rs index 08524b685..e5166ca10 100644 --- a/src/math/generic/mod.rs +++ b/src/math/generic/mod.rs @@ -1,5 +1,7 @@ mod copysign; mod fabs; +mod trunc; pub use copysign::copysign; pub use fabs::fabs; +pub use trunc::trunc; diff --git a/src/math/generic/trunc.rs b/src/math/generic/trunc.rs new file mode 100644 index 000000000..ca5f1bdd6 --- /dev/null +++ b/src/math/generic/trunc.rs @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: MIT + * origin: musl src/math/trunc.c */ + +use super::super::{Float, Int, IntTy, MinInt}; + +pub fn trunc(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::::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); + } +} diff --git a/src/math/mod.rs b/src/math/mod.rs index 5baf35e42..723be0e1d 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -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; @@ -343,9 +343,11 @@ cfg_if! { if #[cfg(f16_enabled)] { mod copysignf16; mod fabsf16; + mod truncf16; pub use self::copysignf16::copysignf16; pub use self::fabsf16::fabsf16; + pub use self::truncf16::truncf16; } } @@ -353,9 +355,11 @@ cfg_if! { if #[cfg(f128_enabled)] { mod copysignf128; mod fabsf128; + mod truncf128; pub use self::copysignf128::copysignf128; pub use self::fabsf128::fabsf128; + pub use self::truncf128::truncf128; } } diff --git a/src/math/trunc.rs b/src/math/trunc.rs index 7e5c4f2c2..2cc8aaa7e 100644 --- a/src/math/trunc.rs +++ b/src/math/trunc.rs @@ -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) } diff --git a/src/math/truncf.rs b/src/math/truncf.rs index b491747d9..14533a267 100644 --- a/src/math/truncf.rs +++ b/src/math/truncf.rs @@ -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 diff --git a/src/math/truncf128.rs b/src/math/truncf128.rs new file mode 100644 index 000000000..9dccc0d0e --- /dev/null +++ b/src/math/truncf128.rs @@ -0,0 +1,7 @@ +/// Rounds the number toward 0 to the closest integral value (f128). +/// +/// This effectively removes the decimal part of the number, leaving the integral part. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn truncf128(x: f128) -> f128 { + super::generic::trunc(x) +} diff --git a/src/math/truncf16.rs b/src/math/truncf16.rs new file mode 100644 index 000000000..d7c3d225c --- /dev/null +++ b/src/math/truncf16.rs @@ -0,0 +1,7 @@ +/// Rounds the number toward 0 to the closest integral value (f16). +/// +/// This effectively removes the decimal part of the number, leaving the integral part. +#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)] +pub fn truncf16(x: f16) -> f16 { + super::generic::trunc(x) +}