about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTrevor Gross <tmgross@umich.edu>2024-06-27 03:21:25 -0400
committerTrevor Gross <tmgross@umich.edu>2024-07-14 18:44:43 -0400
commit3a2c0aedf1bd1371e5e58cbf0f2cf51fca02494e (patch)
tree4d39c87f952c8a4a745d9796ee0fc4dabca0b933
parentcf2df68d1f5e56803c97d91e2b1a9f1c9923c533 (diff)
downloadrust-3a2c0aedf1bd1371e5e58cbf0f2cf51fca02494e.tar.gz
rust-3a2c0aedf1bd1371e5e58cbf0f2cf51fca02494e.zip
Add `classify` and related methods for `f16` and `f128`
-rw-r--r--library/core/src/num/f128.rs120
-rw-r--r--library/core/src/num/f16.rs162
-rw-r--r--library/std/src/f128/tests.rs66
-rw-r--r--library/std/src/f16/tests.rs66
4 files changed, 375 insertions, 39 deletions
diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs
index 58ed98c888c..ad5a5ee9905 100644
--- a/library/core/src/num/f128.rs
+++ b/library/core/src/num/f128.rs
@@ -13,6 +13,7 @@
 
 use crate::convert::FloatToInt;
 use crate::mem;
+use crate::num::FpCategory;
 
 /// Basic mathematical constants.
 #[unstable(feature = "f128", issue = "116909")]
@@ -251,6 +252,12 @@ impl f128 {
     #[cfg(not(bootstrap))]
     pub(crate) const SIGN_MASK: u128 = 0x8000_0000_0000_0000_0000_0000_0000_0000;
 
+    /// Exponent mask
+    pub(crate) const EXP_MASK: u128 = 0x7fff_0000_0000_0000_0000_0000_0000_0000;
+
+    /// Mantissa mask
+    pub(crate) const MAN_MASK: u128 = 0x0000_ffff_ffff_ffff_ffff_ffff_ffff_ffff;
+
     /// Minimum representable positive value (min subnormal)
     #[cfg(not(bootstrap))]
     const TINY_BITS: u128 = 0x1;
@@ -354,6 +361,119 @@ impl f128 {
         self.abs_private() < Self::INFINITY
     }
 
+    /// Returns `true` if the number is [subnormal].
+    ///
+    /// ```
+    /// #![feature(f128)]
+    /// # // FIXME(f16_f128): remove when `eqtf2` is available
+    /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
+    ///
+    /// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
+    /// let max = f128::MAX;
+    /// let lower_than_min = 1.0e-4960_f128;
+    /// let zero = 0.0_f128;
+    ///
+    /// assert!(!min.is_subnormal());
+    /// assert!(!max.is_subnormal());
+    ///
+    /// assert!(!zero.is_subnormal());
+    /// assert!(!f128::NAN.is_subnormal());
+    /// assert!(!f128::INFINITY.is_subnormal());
+    /// // Values between `0` and `min` are Subnormal.
+    /// assert!(lower_than_min.is_subnormal());
+    /// # }
+    /// ```
+    ///
+    /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
+    #[inline]
+    #[must_use]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f128", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn is_subnormal(self) -> bool {
+        matches!(self.classify(), FpCategory::Subnormal)
+    }
+
+    /// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN.
+    ///
+    /// ```
+    /// #![feature(f128)]
+    /// # // FIXME(f16_f128): remove when `eqtf2` is available
+    /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
+    ///
+    /// let min = f128::MIN_POSITIVE; // 3.362103143e-4932f128
+    /// let max = f128::MAX;
+    /// let lower_than_min = 1.0e-4960_f128;
+    /// let zero = 0.0_f128;
+    ///
+    /// assert!(min.is_normal());
+    /// assert!(max.is_normal());
+    ///
+    /// assert!(!zero.is_normal());
+    /// assert!(!f128::NAN.is_normal());
+    /// assert!(!f128::INFINITY.is_normal());
+    /// // Values between `0` and `min` are Subnormal.
+    /// assert!(!lower_than_min.is_normal());
+    /// # }
+    /// ```
+    ///
+    /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
+    #[inline]
+    #[must_use]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f128", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn is_normal(self) -> bool {
+        matches!(self.classify(), FpCategory::Normal)
+    }
+
+    /// 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.
+    ///
+    /// ```
+    /// #![feature(f128)]
+    /// # // FIXME(f16_f128): remove when `eqtf2` is available
+    /// # #[cfg(all(target_arch = "x86_64", target_os = "linux"))] {
+    ///
+    /// use std::num::FpCategory;
+    ///
+    /// let num = 12.4_f128;
+    /// let inf = f128::INFINITY;
+    ///
+    /// assert_eq!(num.classify(), FpCategory::Normal);
+    /// assert_eq!(inf.classify(), FpCategory::Infinite);
+    /// # }
+    /// ```
+    #[inline]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f128", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn classify(self) -> FpCategory {
+        // Other float types cannot use a bitwise classify because they may suffer a variety
+        // of errors if the backend chooses to cast to different float types (x87). `f128` cannot
+        // fit into any other float types so this is not a concern, and we rely on bit patterns.
+
+        // SAFETY: POD bitcast, same as in `to_bits`.
+        let bits = unsafe { mem::transmute::<f128, u128>(self) };
+        Self::classify_bits(bits)
+    }
+
+    /// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
+    /// FIXME(jubilee): In a just world, this would be the entire impl for classify,
+    /// plus a transmute. We do not live in a just world, but we can make it more so.
+    #[inline]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    const fn classify_bits(b: u128) -> FpCategory {
+        match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
+            (0, Self::EXP_MASK) => FpCategory::Infinite,
+            (_, Self::EXP_MASK) => FpCategory::Nan,
+            (0, 0) => FpCategory::Zero,
+            (_, 0) => FpCategory::Subnormal,
+            _ => FpCategory::Normal,
+        }
+    }
+
     /// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
     /// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
     /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs
index 3c58b0af9c2..198a506f2b8 100644
--- a/library/core/src/num/f16.rs
+++ b/library/core/src/num/f16.rs
@@ -13,6 +13,7 @@
 
 use crate::convert::FloatToInt;
 use crate::mem;
+use crate::num::FpCategory;
 
 /// Basic mathematical constants.
 #[unstable(feature = "f16", issue = "116909")]
@@ -244,7 +245,13 @@ impl f16 {
 
     /// Sign bit
     #[cfg(not(bootstrap))]
-    const SIGN_MASK: u16 = 0x8000;
+    pub(crate) const SIGN_MASK: u16 = 0x8000;
+
+    /// Exponent mask
+    pub(crate) const EXP_MASK: u16 = 0x7c00;
+
+    /// Mantissa mask
+    pub(crate) const MAN_MASK: u16 = 0x03ff;
 
     /// Minimum representable positive value (min subnormal)
     #[cfg(not(bootstrap))]
@@ -344,6 +351,159 @@ impl f16 {
         self.abs_private() < Self::INFINITY
     }
 
+    /// Returns `true` if the number is [subnormal].
+    ///
+    /// ```
+    /// #![feature(f16)]
+    /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
+    ///
+    /// let min = f16::MIN_POSITIVE; // 6.1035e-5
+    /// let max = f16::MAX;
+    /// let lower_than_min = 1.0e-7_f16;
+    /// let zero = 0.0_f16;
+    ///
+    /// assert!(!min.is_subnormal());
+    /// assert!(!max.is_subnormal());
+    ///
+    /// assert!(!zero.is_subnormal());
+    /// assert!(!f16::NAN.is_subnormal());
+    /// assert!(!f16::INFINITY.is_subnormal());
+    /// // Values between `0` and `min` are Subnormal.
+    /// assert!(lower_than_min.is_subnormal());
+    /// # }
+    /// ```
+    /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
+    #[inline]
+    #[must_use]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f16", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn is_subnormal(self) -> bool {
+        matches!(self.classify(), FpCategory::Subnormal)
+    }
+
+    /// Returns `true` if the number is neither zero, infinite, [subnormal], or NaN.
+    ///
+    /// ```
+    /// #![feature(f16)]
+    /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
+    ///
+    /// let min = f16::MIN_POSITIVE; // 6.1035e-5
+    /// let max = f16::MAX;
+    /// let lower_than_min = 1.0e-7_f16;
+    /// let zero = 0.0_f16;
+    ///
+    /// assert!(min.is_normal());
+    /// assert!(max.is_normal());
+    ///
+    /// assert!(!zero.is_normal());
+    /// assert!(!f16::NAN.is_normal());
+    /// assert!(!f16::INFINITY.is_normal());
+    /// // Values between `0` and `min` are Subnormal.
+    /// assert!(!lower_than_min.is_normal());
+    /// # }
+    /// ```
+    /// [subnormal]: https://en.wikipedia.org/wiki/Denormal_number
+    #[inline]
+    #[must_use]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f16", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn is_normal(self) -> bool {
+        matches!(self.classify(), FpCategory::Normal)
+    }
+
+    /// 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.
+    ///
+    /// ```
+    /// #![feature(f16)]
+    /// # #[cfg(target_arch = "aarch64")] { // FIXME(f16_F128): rust-lang/rust#123885
+    ///
+    /// use std::num::FpCategory;
+    ///
+    /// let num = 12.4_f16;
+    /// let inf = f16::INFINITY;
+    ///
+    /// assert_eq!(num.classify(), FpCategory::Normal);
+    /// assert_eq!(inf.classify(), FpCategory::Infinite);
+    /// # }
+    /// ```
+    #[inline]
+    #[cfg(not(bootstrap))]
+    #[unstable(feature = "f16", issue = "116909")]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    pub const fn classify(self) -> FpCategory {
+        // A previous implementation for f32/f64 tried to only use bitmask-based checks,
+        // using `to_bits` to transmute the float to its bit repr and match on that.
+        // Unfortunately, floating point numbers can be much worse than that.
+        // This also needs to not result in recursive evaluations of `to_bits`.
+        //
+
+        // Platforms without native support generally convert to `f32` to perform operations,
+        // and most of these platforms correctly round back to `f16` after each operation.
+        // However, some platforms have bugs where they keep the excess `f32` precision (e.g.
+        // WASM, see llvm/llvm-project#96437). This implementation makes a best-effort attempt
+        // to account for that excess precision.
+        if self.is_infinite() {
+            // Thus, a value may compare unequal to infinity, despite having a "full" exponent mask.
+            FpCategory::Infinite
+        } else if self.is_nan() {
+            // And it may not be NaN, as it can simply be an "overextended" finite value.
+            FpCategory::Nan
+        } else {
+            // However, std can't simply compare to zero to check for zero, either,
+            // as correctness requires avoiding equality tests that may be Subnormal == -0.0
+            // because it may be wrong under "denormals are zero" and "flush to zero" modes.
+            // Most of std's targets don't use those, but they are used for thumbv7neon.
+            // So, this does use bitpattern matching for the rest.
+
+            // SAFETY: f16 to u16 is fine. Usually.
+            // If classify has gotten this far, the value is definitely in one of these categories.
+            unsafe { f16::partial_classify(self) }
+        }
+    }
+
+    /// This doesn't actually return a right answer for NaN on purpose,
+    /// seeing as how it cannot correctly discern between a floating point NaN,
+    /// and some normal floating point numbers truncated from an x87 FPU.
+    ///
+    /// # Safety
+    ///
+    /// This requires making sure you call this function for values it answers correctly on,
+    /// otherwise it returns a wrong answer. This is not important for memory safety per se,
+    /// but getting floats correct is important for not accidentally leaking const eval
+    /// runtime-deviating logic which may or may not be acceptable.
+    #[inline]
+    #[cfg(not(bootstrap))]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    const unsafe fn partial_classify(self) -> FpCategory {
+        // SAFETY: The caller is not asking questions for which this will tell lies.
+        let b = unsafe { mem::transmute::<f16, u16>(self) };
+        match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
+            (0, Self::EXP_MASK) => FpCategory::Infinite,
+            (0, 0) => FpCategory::Zero,
+            (_, 0) => FpCategory::Subnormal,
+            _ => FpCategory::Normal,
+        }
+    }
+
+    /// This operates on bits, and only bits, so it can ignore concerns about weird FPUs.
+    /// FIXME(jubilee): In a just world, this would be the entire impl for classify,
+    /// plus a transmute. We do not live in a just world, but we can make it more so.
+    #[inline]
+    #[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
+    const fn classify_bits(b: u16) -> FpCategory {
+        match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
+            (0, Self::EXP_MASK) => FpCategory::Infinite,
+            (_, Self::EXP_MASK) => FpCategory::Nan,
+            (0, 0) => FpCategory::Zero,
+            (_, 0) => FpCategory::Subnormal,
+            _ => FpCategory::Normal,
+        }
+    }
+
     /// Returns `true` if `self` has a positive sign, including `+0.0`, NaNs with
     /// positive sign bit and positive infinity. Note that IEEE 754 doesn't assign any
     /// meaning to the sign bit in case of a NaN, and as Rust doesn't guarantee that
diff --git a/library/std/src/f128/tests.rs b/library/std/src/f128/tests.rs
index bd7a921c502..0b3e485b0e7 100644
--- a/library/std/src/f128/tests.rs
+++ b/library/std/src/f128/tests.rs
@@ -3,6 +3,7 @@
 #![cfg(reliable_f128)]
 
 use crate::f128::consts;
+use crate::num::FpCategory as Fp;
 use crate::num::*;
 
 /// Smallest number
@@ -52,9 +53,8 @@ fn test_nan() {
     assert!(!nan.is_finite());
     assert!(nan.is_sign_positive());
     assert!(!nan.is_sign_negative());
-    // FIXME(f16_f128): classify
-    // assert!(!nan.is_normal());
-    // assert_eq!(Fp::Nan, nan.classify());
+    assert!(!nan.is_normal());
+    assert_eq!(Fp::Nan, nan.classify());
 }
 
 #[test]
@@ -65,9 +65,8 @@ fn test_infinity() {
     assert!(inf.is_sign_positive());
     assert!(!inf.is_sign_negative());
     assert!(!inf.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!inf.is_normal());
-    // assert_eq!(Fp::Infinite, inf.classify());
+    assert!(!inf.is_normal());
+    assert_eq!(Fp::Infinite, inf.classify());
 }
 
 #[test]
@@ -78,9 +77,8 @@ fn test_neg_infinity() {
     assert!(!neg_inf.is_sign_positive());
     assert!(neg_inf.is_sign_negative());
     assert!(!neg_inf.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!neg_inf.is_normal());
-    // assert_eq!(Fp::Infinite, neg_inf.classify());
+    assert!(!neg_inf.is_normal());
+    assert_eq!(Fp::Infinite, neg_inf.classify());
 }
 
 #[test]
@@ -92,9 +90,8 @@ fn test_zero() {
     assert!(zero.is_sign_positive());
     assert!(!zero.is_sign_negative());
     assert!(!zero.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!zero.is_normal());
-    // assert_eq!(Fp::Zero, zero.classify());
+    assert!(!zero.is_normal());
+    assert_eq!(Fp::Zero, zero.classify());
 }
 
 #[test]
@@ -106,9 +103,8 @@ fn test_neg_zero() {
     assert!(!neg_zero.is_sign_positive());
     assert!(neg_zero.is_sign_negative());
     assert!(!neg_zero.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!neg_zero.is_normal());
-    // assert_eq!(Fp::Zero, neg_zero.classify());
+    assert!(!neg_zero.is_normal());
+    assert_eq!(Fp::Zero, neg_zero.classify());
 }
 
 #[test]
@@ -120,9 +116,8 @@ fn test_one() {
     assert!(one.is_sign_positive());
     assert!(!one.is_sign_negative());
     assert!(!one.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(one.is_normal());
-    // assert_eq!(Fp::Normal, one.classify());
+    assert!(one.is_normal());
+    assert_eq!(Fp::Normal, one.classify());
 }
 
 #[test]
@@ -164,7 +159,40 @@ fn test_is_finite() {
     assert!((-109.2f128).is_finite());
 }
 
-// FIXME(f16_f128): add `test_is_normal` and `test_classify` when classify is working
+#[test]
+fn test_is_normal() {
+    let nan: f128 = f128::NAN;
+    let inf: f128 = f128::INFINITY;
+    let neg_inf: f128 = f128::NEG_INFINITY;
+    let zero: f128 = 0.0f128;
+    let neg_zero: f128 = -0.0;
+    assert!(!nan.is_normal());
+    assert!(!inf.is_normal());
+    assert!(!neg_inf.is_normal());
+    assert!(!zero.is_normal());
+    assert!(!neg_zero.is_normal());
+    assert!(1f128.is_normal());
+    assert!(1e-4931f128.is_normal());
+    assert!(!1e-4932f128.is_normal());
+}
+
+#[test]
+fn test_classify() {
+    let nan: f128 = f128::NAN;
+    let inf: f128 = f128::INFINITY;
+    let neg_inf: f128 = f128::NEG_INFINITY;
+    let zero: f128 = 0.0f128;
+    let neg_zero: f128 = -0.0;
+    assert_eq!(nan.classify(), Fp::Nan);
+    assert_eq!(inf.classify(), Fp::Infinite);
+    assert_eq!(neg_inf.classify(), Fp::Infinite);
+    assert_eq!(zero.classify(), Fp::Zero);
+    assert_eq!(neg_zero.classify(), Fp::Zero);
+    assert_eq!(1f128.classify(), Fp::Normal);
+    assert_eq!(1e-4931f128.classify(), Fp::Normal);
+    assert_eq!(1e-4932f128.classify(), Fp::Subnormal);
+}
+
 // FIXME(f16_f128): add missing math functions when available
 
 #[test]
diff --git a/library/std/src/f16/tests.rs b/library/std/src/f16/tests.rs
index bb6a811529e..26658a0be87 100644
--- a/library/std/src/f16/tests.rs
+++ b/library/std/src/f16/tests.rs
@@ -3,6 +3,7 @@
 #![cfg(reliable_f16)]
 
 use crate::f16::consts;
+use crate::num::FpCategory as Fp;
 use crate::num::*;
 
 // We run out of precision pretty quickly with f16
@@ -58,9 +59,8 @@ fn test_nan() {
     assert!(!nan.is_finite());
     assert!(nan.is_sign_positive());
     assert!(!nan.is_sign_negative());
-    // FIXME(f16_f128): classify
-    // assert!(!nan.is_normal());
-    // assert_eq!(Fp::Nan, nan.classify());
+    assert!(!nan.is_normal());
+    assert_eq!(Fp::Nan, nan.classify());
 }
 
 #[test]
@@ -71,9 +71,8 @@ fn test_infinity() {
     assert!(inf.is_sign_positive());
     assert!(!inf.is_sign_negative());
     assert!(!inf.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!inf.is_normal());
-    // assert_eq!(Fp::Infinite, inf.classify());
+    assert!(!inf.is_normal());
+    assert_eq!(Fp::Infinite, inf.classify());
 }
 
 #[test]
@@ -84,9 +83,8 @@ fn test_neg_infinity() {
     assert!(!neg_inf.is_sign_positive());
     assert!(neg_inf.is_sign_negative());
     assert!(!neg_inf.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!neg_inf.is_normal());
-    // assert_eq!(Fp::Infinite, neg_inf.classify());
+    assert!(!neg_inf.is_normal());
+    assert_eq!(Fp::Infinite, neg_inf.classify());
 }
 
 #[test]
@@ -98,9 +96,8 @@ fn test_zero() {
     assert!(zero.is_sign_positive());
     assert!(!zero.is_sign_negative());
     assert!(!zero.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!zero.is_normal());
-    // assert_eq!(Fp::Zero, zero.classify());
+    assert!(!zero.is_normal());
+    assert_eq!(Fp::Zero, zero.classify());
 }
 
 #[test]
@@ -112,9 +109,8 @@ fn test_neg_zero() {
     assert!(!neg_zero.is_sign_positive());
     assert!(neg_zero.is_sign_negative());
     assert!(!neg_zero.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(!neg_zero.is_normal());
-    // assert_eq!(Fp::Zero, neg_zero.classify());
+    assert!(!neg_zero.is_normal());
+    assert_eq!(Fp::Zero, neg_zero.classify());
 }
 
 #[test]
@@ -126,9 +122,8 @@ fn test_one() {
     assert!(one.is_sign_positive());
     assert!(!one.is_sign_negative());
     assert!(!one.is_nan());
-    // FIXME(f16_f128): classify
-    // assert!(one.is_normal());
-    // assert_eq!(Fp::Normal, one.classify());
+    assert!(one.is_normal());
+    assert_eq!(Fp::Normal, one.classify());
 }
 
 #[test]
@@ -170,7 +165,40 @@ fn test_is_finite() {
     assert!((-109.2f16).is_finite());
 }
 
-// FIXME(f16_f128): add `test_is_normal` and `test_classify` when classify is working
+#[test]
+fn test_is_normal() {
+    let nan: f16 = f16::NAN;
+    let inf: f16 = f16::INFINITY;
+    let neg_inf: f16 = f16::NEG_INFINITY;
+    let zero: f16 = 0.0f16;
+    let neg_zero: f16 = -0.0;
+    assert!(!nan.is_normal());
+    assert!(!inf.is_normal());
+    assert!(!neg_inf.is_normal());
+    assert!(!zero.is_normal());
+    assert!(!neg_zero.is_normal());
+    assert!(1f16.is_normal());
+    assert!(1e-4f16.is_normal());
+    assert!(!1e-5f16.is_normal());
+}
+
+#[test]
+fn test_classify() {
+    let nan: f16 = f16::NAN;
+    let inf: f16 = f16::INFINITY;
+    let neg_inf: f16 = f16::NEG_INFINITY;
+    let zero: f16 = 0.0f16;
+    let neg_zero: f16 = -0.0;
+    assert_eq!(nan.classify(), Fp::Nan);
+    assert_eq!(inf.classify(), Fp::Infinite);
+    assert_eq!(neg_inf.classify(), Fp::Infinite);
+    assert_eq!(zero.classify(), Fp::Zero);
+    assert_eq!(neg_zero.classify(), Fp::Zero);
+    assert_eq!(1f16.classify(), Fp::Normal);
+    assert_eq!(1e-4f16.classify(), Fp::Normal);
+    assert_eq!(1e-5f16.classify(), Fp::Subnormal);
+}
+
 // FIXME(f16_f128): add missing math functions when available
 
 #[test]