From 9d85e9e1ee129c79fdef7a73200a657f113e69c8 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Tue, 28 Mar 2023 07:57:15 -0400 Subject: [PATCH 1/6] +Float::is_subnormal --- src/float.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/float.rs b/src/float.rs index 33cbf9cf..07b3b6b9 100644 --- a/src/float.rs +++ b/src/float.rs @@ -240,6 +240,28 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { fn is_normal(self) -> bool { self.classify() == FpCategory::Normal } + + /// Returns `true` if the number is [subnormal]. + /// + /// ``` + /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308_f64 + /// let max = f64::MAX; + /// let lower_than_min = 1.0e-308_f64; + /// let zero = 0.0_f64; + /// + /// assert!(!min.is_subnormal()); + /// assert!(!max.is_subnormal()); + /// + /// assert!(!zero.is_subnormal()); + /// assert!(!f64::NAN.is_subnormal()); + /// assert!(!f64::INFINITY.is_subnormal()); + /// // Values between `0` and `min` are Subnormal. + /// assert!(lower_than_min.is_subnormal()); + /// ``` + /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number + fn is_subnormal(self) -> bool { + self.classify() == FpCategory::Subnormal + } /// Returns the floating point category of the number. If only one property /// is going to be tested, it is generally faster to use the specific @@ -900,6 +922,7 @@ impl FloatCore for f64 { Self::is_infinite(self) -> bool; Self::is_finite(self) -> bool; Self::is_normal(self) -> bool; + Self::is_subnormal(self) -> bool; Self::classify(self) -> FpCategory; Self::floor(self) -> Self; Self::ceil(self) -> Self; @@ -1126,6 +1149,26 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg { /// [subnormal]: http://en.wikipedia.org/wiki/Denormal_number fn is_normal(self) -> bool; + /// Returns `true` if the number is [subnormal]. + /// + /// ``` + /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308_f64 + /// let max = f64::MAX; + /// let lower_than_min = 1.0e-308_f64; + /// let zero = 0.0_f64; + /// + /// assert!(!min.is_subnormal()); + /// assert!(!max.is_subnormal()); + /// + /// assert!(!zero.is_subnormal()); + /// assert!(!f64::NAN.is_subnormal()); + /// assert!(!f64::INFINITY.is_subnormal()); + /// // Values between `0` and `min` are Subnormal. + /// assert!(lower_than_min.is_subnormal()); + /// ``` + /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number + fn is_subnormal(self) -> bool; + /// Returns the floating point category of the number. If only one property /// is going to be tested, it is generally faster to use the specific /// predicate instead. @@ -1913,6 +1956,7 @@ macro_rules! float_impl_std { Self::is_infinite(self) -> bool; Self::is_finite(self) -> bool; Self::is_normal(self) -> bool; + Self::is_subnormal(self) -> bool; Self::classify(self) -> FpCategory; Self::floor(self) -> Self; Self::ceil(self) -> Self; From 22509ff909c490e37975dd63afea3370f7094ccf Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Tue, 28 Mar 2023 08:56:14 -0400 Subject: [PATCH 2/6] Add tests for is_subnormal() --- src/float.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/float.rs b/src/float.rs index 07b3b6b9..5eab491a 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2362,6 +2362,7 @@ mod tests { assert!(p.is_sign_positive()); assert!(n.is_sign_negative()); assert!(nan.is_nan()); + assert!(!nan.is_subnormal()); assert_eq!(p, p.copysign(p)); assert_eq!(p.neg(), p.copysign(n)); @@ -2372,4 +2373,23 @@ mod tests { assert!(nan.copysign(p).is_sign_positive()); assert!(nan.copysign(n).is_sign_negative()); } + + #[cfg(any(feature = "std", feature = "libm"))] + fn test_subnormal() { + let lower_than_min: F = F::from(1.0e-308_f64).unwrap(); + assert!(lower_than_min.is_subnormal()); + } + + #[test] + #[cfg(any(feature = "std", feature = "libm"))] + fn subnormal() { + test_subnormal::(); + } + + #[test] + #[should_panic] + #[cfg(any(feature = "std", feature = "libm"))] + fn subnormal_f32() { + test_subnormal::(); + } } From 19cb6f0452e07f6bff449882632fb81e07a585f0 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 20 Jul 2023 13:21:45 -0700 Subject: [PATCH 3/6] Add a default impl for `Float::is_subnormal` Any new trait method require a default, or else it's a breaking change. --- src/float.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/float.rs b/src/float.rs index 5eab491a..79aced72 100644 --- a/src/float.rs +++ b/src/float.rs @@ -240,7 +240,7 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { fn is_normal(self) -> bool { self.classify() == FpCategory::Normal } - + /// Returns `true` if the number is [subnormal]. /// /// ``` @@ -258,7 +258,8 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { /// // Values between `0` and `min` are Subnormal. /// assert!(lower_than_min.is_subnormal()); /// ``` - /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number + /// [subnormal]: https://en.wikipedia.org/wiki/Subnormal_number + #[inline] fn is_subnormal(self) -> bool { self.classify() == FpCategory::Subnormal } @@ -1146,7 +1147,7 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg { /// // Values between `0` and `min` are Subnormal. /// assert!(!lower_than_min.is_normal()); /// ``` - /// [subnormal]: http://en.wikipedia.org/wiki/Denormal_number + /// [subnormal]: http://en.wikipedia.org/wiki/Subnormal_number fn is_normal(self) -> bool; /// Returns `true` if the number is [subnormal]. @@ -1166,8 +1167,11 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg { /// // Values between `0` and `min` are Subnormal. /// assert!(lower_than_min.is_subnormal()); /// ``` - /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number - fn is_subnormal(self) -> bool; + /// [subnormal]: https://en.wikipedia.org/wiki/Subnormal_number + #[inline] + fn is_subnormal(self) -> bool { + self.classify() == FpCategory::Subnormal + } /// Returns the floating point category of the number. If only one property /// is going to be tested, it is generally faster to use the specific From f5dc702b1c78aa475b0b0066e957dfbd6fd56e58 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 20 Jul 2023 13:26:33 -0700 Subject: [PATCH 4/6] Check the existence of is_subnormal (1.53) before forwarding --- .github/workflows/ci.yaml | 1 + bors.toml | 1 + build.rs | 1 + ci/rustup.sh | 2 +- src/float.rs | 17 ++++++++++++----- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 261f2440..95eb8264 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,6 +19,7 @@ jobs: 1.38.0, # has_div_euclid 1.44.0, # has_to_int_unchecked 1.46.0, # has_leading_trailing_ones + 1.53.0, # has_is_subnormal stable, beta, nightly, diff --git a/bors.toml b/bors.toml index fa15ce17..7314c271 100644 --- a/bors.toml +++ b/bors.toml @@ -5,6 +5,7 @@ status = [ "Test (1.38.0)", "Test (1.44.0)", "Test (1.46.0)", + "Test (1.53.0)", "Test (stable)", "Test (beta)", "Test (nightly)", diff --git a/build.rs b/build.rs index 639d0582..b0aa7d65 100644 --- a/build.rs +++ b/build.rs @@ -15,6 +15,7 @@ fn main() { if env::var_os("CARGO_FEATURE_STD").is_some() { ac.emit_expression_cfg("1f64.copysign(-1f64)", "has_copysign"); } + ac.emit_expression_cfg("1f64.is_subnormal()", "has_is_subnormal"); autocfg::rerun_path("build.rs"); } diff --git a/ci/rustup.sh b/ci/rustup.sh index fb3da186..56a8df10 100755 --- a/ci/rustup.sh +++ b/ci/rustup.sh @@ -5,6 +5,6 @@ set -ex ci=$(dirname $0) -for version in 1.31.0 1.35.0 1.37.0 1.38.0 1.44.0 1.46.0 stable beta nightly; do +for version in 1.31.0 1.35.0 1.37.0 1.38.0 1.44.0 1.46.0 1.53.0 stable beta nightly; do rustup run "$version" "$ci/test_full.sh" done diff --git a/src/float.rs b/src/float.rs index 79aced72..d5fcf4a2 100644 --- a/src/float.rs +++ b/src/float.rs @@ -923,7 +923,6 @@ impl FloatCore for f64 { Self::is_infinite(self) -> bool; Self::is_finite(self) -> bool; Self::is_normal(self) -> bool; - Self::is_subnormal(self) -> bool; Self::classify(self) -> FpCategory; Self::floor(self) -> Self; Self::ceil(self) -> Self; @@ -942,6 +941,11 @@ impl FloatCore for f64 { Self::to_radians(self) -> Self; } + #[cfg(has_is_subnormal)] + forward! { + Self::is_subnormal(self) -> bool; + } + #[cfg(all(not(feature = "std"), feature = "libm"))] forward! { libm::floor as floor(self) -> Self; @@ -1960,7 +1964,6 @@ macro_rules! float_impl_std { Self::is_infinite(self) -> bool; Self::is_finite(self) -> bool; Self::is_normal(self) -> bool; - Self::is_subnormal(self) -> bool; Self::classify(self) -> FpCategory; Self::floor(self) -> Self; Self::ceil(self) -> Self; @@ -2007,9 +2010,13 @@ macro_rules! float_impl_std { } #[cfg(has_copysign)] - #[inline] - fn copysign(self, sign: Self) -> Self { - Self::copysign(self, sign) + forward! { + Self::copysign(self, sign: Self) -> Self; + } + + #[cfg(has_is_subnormal)] + forward! { + Self::is_subnormal(self) -> bool; } } }; From 90b89817138b9a3003008c2b1f4677398348c1bf Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 20 Jul 2023 13:42:44 -0700 Subject: [PATCH 5/6] Simplify is_subnormal tests --- src/float.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/float.rs b/src/float.rs index d5fcf4a2..90851b7e 100644 --- a/src/float.rs +++ b/src/float.rs @@ -2387,7 +2387,9 @@ mod tests { #[cfg(any(feature = "std", feature = "libm"))] fn test_subnormal() { - let lower_than_min: F = F::from(1.0e-308_f64).unwrap(); + let min_positive = F::min_positive_value(); + let lower_than_min = min_positive / F::from(2.0f32).unwrap(); + assert!(!min_positive.is_subnormal()); assert!(lower_than_min.is_subnormal()); } @@ -2395,12 +2397,6 @@ mod tests { #[cfg(any(feature = "std", feature = "libm"))] fn subnormal() { test_subnormal::(); - } - - #[test] - #[should_panic] - #[cfg(any(feature = "std", feature = "libm"))] - fn subnormal_f32() { test_subnormal::(); } } From 58b46b143faed7f02795129526d9ee084c93d16f Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Thu, 20 Jul 2023 13:56:38 -0700 Subject: [PATCH 6/6] Fix is_subnormal doc tests --- src/float.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/float.rs b/src/float.rs index 90851b7e..26a6d63b 100644 --- a/src/float.rs +++ b/src/float.rs @@ -244,6 +244,9 @@ pub trait FloatCore: Num + NumCast + Neg + PartialOrd + Copy { /// Returns `true` if the number is [subnormal]. /// /// ``` + /// use num_traits::float::FloatCore; + /// use std::f64; + /// /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308_f64 /// let max = f64::MAX; /// let lower_than_min = 1.0e-308_f64; @@ -1157,6 +1160,9 @@ pub trait Float: Num + Copy + NumCast + PartialOrd + Neg { /// Returns `true` if the number is [subnormal]. /// /// ``` + /// use num_traits::Float; + /// use std::f64; + /// /// let min = f64::MIN_POSITIVE; // 2.2250738585072014e-308_f64 /// let max = f64::MAX; /// let lower_than_min = 1.0e-308_f64;