diff --git a/README.md b/README.md index 3d73794..ff12eae 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Every function produces identical results to Python at the binary representation ## Compatibility Status -### math (56/56) +### math (61/61) - [x] `acos` - [x] `acosh` @@ -38,6 +38,8 @@ Every function produces identical results to Python at the binary representation - [x] `fabs` - [x] `floor` - [x] `fma` +- [x] `fmax` +- [x] `fmin` - [x] `fmod` - [x] `frexp` - [x] `fsum` @@ -48,6 +50,8 @@ Every function produces identical results to Python at the binary representation - [x] `isfinite` - [x] `isinf` - [x] `isnan` +- [x] `isnormal` +- [x] `issubnormal` - [x] `ldexp` - [x] `lgamma` - [x] `log` @@ -63,6 +67,7 @@ Every function produces identical results to Python at the binary representation - [x] `radians` - [x] `remainder` - [x] `sin` +- [x] `signbit` - [x] `sinh` - [x] `sqrt` - [x] `sumprod` (`sumprod`, `sumprod_int`) diff --git a/src/m.rs b/src/m.rs index 259ef22..080d6c4 100644 --- a/src/m.rs +++ b/src/m.rs @@ -157,6 +157,16 @@ pub fn floor(n: f64) -> f64 { unsafe { m_sys::floor(n) } } +#[inline(always)] +pub fn fmax(x: f64, y: f64) -> f64 { + unsafe { m_sys::fmax(x, y) } +} + +#[inline(always)] +pub fn fmin(x: f64, y: f64) -> f64 { + unsafe { m_sys::fmin(x, y) } +} + #[inline(always)] pub fn fmod(x: f64, y: f64) -> f64 { unsafe { m_sys::fmod(x, y) } diff --git a/src/m_rust.rs b/src/m_rust.rs index fa9ba14..6c9ea7f 100644 --- a/src/m_rust.rs +++ b/src/m_rust.rs @@ -151,6 +151,16 @@ pub fn floor(n: f64) -> f64 { libm::floor(n) } +#[inline(always)] +pub fn fmax(x: f64, y: f64) -> f64 { + libm::fmax(x, y) +} + +#[inline(always)] +pub fn fmin(x: f64, y: f64) -> f64 { + libm::fmin(x, y) +} + #[inline(always)] pub fn fmod(x: f64, y: f64) -> f64 { libm::fmod(x, y) diff --git a/src/m_sys.rs b/src/m_sys.rs index 60406a8..1679ce8 100644 --- a/src/m_sys.rs +++ b/src/m_sys.rs @@ -48,6 +48,8 @@ unsafe extern "C" { // pub fn fdim(a: f64, b: f64) -> f64; pub fn fdimf(a: f32, b: f32) -> f32; pub fn floor(n: f64) -> f64; + pub fn fmax(x: f64, y: f64) -> f64; + pub fn fmin(x: f64, y: f64) -> f64; pub fn fmod(x: f64, y: f64) -> f64; pub fn frexp(n: f64, exp: *mut i32) -> f64; pub fn ldexp(x: f64, n: i32) -> f64; diff --git a/src/math.rs b/src/math.rs index 02e0a31..ae0f4bc 100644 --- a/src/math.rs +++ b/src/math.rs @@ -18,8 +18,8 @@ pub use bigint::{comb_bigint, ldexp_bigint, log_bigint, log2_bigint, log10_bigin pub use exponential::{cbrt, exp, exp2, expm1, log, log1p, log2, log10, pow, sqrt}; pub use gamma::{erf, erfc, gamma, lgamma}; pub use misc::{ - ceil, copysign, fabs, floor, fma, fmod, frexp, isclose, isfinite, isinf, isnan, ldexp, modf, - nextafter, remainder, trunc, ulp, + ceil, copysign, fabs, floor, fma, fmax, fmin, fmod, frexp, isclose, isfinite, isinf, isnan, + isnormal, issubnormal, ldexp, modf, nextafter, remainder, signbit, trunc, ulp, }; pub use trigonometric::{ acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, sin, sinh, tan, tanh, diff --git a/src/math/misc.rs b/src/math/misc.rs index adbe40c..c855774 100644 --- a/src/math/misc.rs +++ b/src/math/misc.rs @@ -3,6 +3,7 @@ use crate::{Error, Result, m}; super::libm_simple!(@1 ceil, floor, trunc); +super::libm_simple!(@2 fmax, fmin); /// Return the next floating-point value after x towards y. /// @@ -104,6 +105,24 @@ pub fn isnan(x: f64) -> bool { x.is_nan() } +/// Return True if x is normal, False otherwise. +#[inline] +pub fn isnormal(x: f64) -> bool { + x.is_normal() +} + +/// Return True if x is subnormal, False otherwise. +#[inline] +pub fn issubnormal(x: f64) -> bool { + x.is_subnormal() +} + +/// Return True if the sign of x is negative, False otherwise. +#[inline] +pub fn signbit(x: f64) -> bool { + x.is_sign_negative() +} + /// Return True if a and b are close to each other. /// /// Whether or not two values are considered close is determined according to @@ -259,6 +278,59 @@ pub fn ulp(x: f64) -> f64 { mod tests { use super::*; + #[test] + fn edgetest_python315_classification() { + for &x in crate::test::EDGE_VALUES { + let bits = x.to_bits(); + let exponent = (bits >> 52) & 0x7ff; + let fraction = bits & ((1_u64 << 52) - 1); + assert_eq!(isnormal(x), exponent != 0 && exponent != 0x7ff); + assert_eq!(issubnormal(x), exponent == 0 && fraction != 0); + assert_eq!(signbit(x), bits >> 63 != 0); + } + } + + #[test] + fn edgetest_python315_extrema() { + for &x in crate::test::EDGE_VALUES { + for &y in crate::test::EDGE_VALUES { + for (result, maximum) in [(fmax(x, y), true), (fmin(x, y), false)] { + // Observe the computed value: LLVM can infer a non-NaN result + // from one numeric operand, but native extrema may quiet an + // sNaN to NaN (notably in optimized AArch64 builds). + let result = std::hint::black_box(result); + if x.is_nan() && y.is_nan() { + assert!(result.is_nan()); + } else if x.is_nan() || y.is_nan() { + // Signaling NaNs can be quieted by the platform libm. + let signaling = |v: f64| v.is_nan() && v.to_bits() & (1_u64 << 51) == 0; + let expected = if x.is_nan() { y } else { x }; + if !signaling(x) && !signaling(y) { + assert_eq!(result.to_bits(), expected.to_bits()); + } else { + assert!( + result.is_nan() || result.to_bits() == expected.to_bits(), + "f{}(0x{:016x}, 0x{:016x}) = 0x{:016x}; expected NaN or 0x{:016x}", + if maximum { "max" } else { "min" }, + x.to_bits(), + y.to_bits(), + result.to_bits(), + expected.to_bits(), + ); + } + } else { + let expected = if (x > y) == maximum { x } else { y }; + // The sign of a zero tie is platform-dependent. + assert_eq!(result, expected); + if x != y { + assert_eq!(result.to_bits(), expected.to_bits()); + } + } + } + } + } + } + /// Edge integer values for testing functions like ldexp const EDGE_INTS: &[i32] = &[0, 1, -1, 100, -100, 1024, -1024, i32::MAX, i32::MIN];