about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/compiler-builtins/libm/crates/libm-test/src/f8_impl.rs6
-rw-r--r--library/compiler-builtins/libm/crates/libm-test/src/lib.rs2
-rw-r--r--library/compiler-builtins/libm/crates/util/src/main.rs11
-rw-r--r--library/compiler-builtins/libm/src/math/support/hex_float.rs244
-rw-r--r--library/compiler-builtins/libm/src/math/support/mod.rs4
5 files changed, 250 insertions, 17 deletions
diff --git a/library/compiler-builtins/libm/crates/libm-test/src/f8_impl.rs b/library/compiler-builtins/libm/crates/libm-test/src/f8_impl.rs
index 96b78392453..5dce9be1891 100644
--- a/library/compiler-builtins/libm/crates/libm-test/src/f8_impl.rs
+++ b/library/compiler-builtins/libm/crates/libm-test/src/f8_impl.rs
@@ -3,6 +3,8 @@
 use std::cmp::{self, Ordering};
 use std::{fmt, ops};
 
+use libm::support::hex_float::parse_any;
+
 use crate::Float;
 
 /// Sometimes verifying float logic is easiest when all values can quickly be checked exhaustively
@@ -490,3 +492,7 @@ impl fmt::LowerHex for f8 {
         self.0.fmt(f)
     }
 }
+
+pub const fn hf8(s: &str) -> f8 {
+    f8(parse_any(s, 8, 3) as u8)
+}
diff --git a/library/compiler-builtins/libm/crates/libm-test/src/lib.rs b/library/compiler-builtins/libm/crates/libm-test/src/lib.rs
index 78b011b1f23..d2fef232505 100644
--- a/library/compiler-builtins/libm/crates/libm-test/src/lib.rs
+++ b/library/compiler-builtins/libm/crates/libm-test/src/lib.rs
@@ -20,7 +20,7 @@ use std::path::PathBuf;
 use std::sync::LazyLock;
 use std::time::SystemTime;
 
-pub use f8_impl::f8;
+pub use f8_impl::{f8, hf8};
 pub use libm::support::{Float, Int, IntTy, MinInt};
 pub use num::{FloatExt, linear_ints, logspace};
 pub use op::{
diff --git a/library/compiler-builtins/libm/crates/util/src/main.rs b/library/compiler-builtins/libm/crates/util/src/main.rs
index 6ea1be3d9ae..357df6b4fe2 100644
--- a/library/compiler-builtins/libm/crates/util/src/main.rs
+++ b/library/compiler-builtins/libm/crates/util/src/main.rs
@@ -8,7 +8,7 @@ use std::env;
 use std::num::ParseIntError;
 use std::str::FromStr;
 
-use libm::support::{hf32, hf64};
+use libm::support::{Hexf, hf32, hf64};
 #[cfg(feature = "build-mpfr")]
 use libm_test::mpfloat::MpOp;
 use libm_test::{MathOp, TupleCall};
@@ -73,7 +73,7 @@ macro_rules! handle_call {
                 }
                 _ => panic!("unrecognized or disabled basis '{}'", $basis),
             };
-            println!("{output:?}");
+            println!("{output:?} {:x}", Hexf(output));
             return;
         }
     };
@@ -303,6 +303,10 @@ impl FromStrRadix for i32 {
 #[cfg(f16_enabled)]
 impl FromStrRadix for f16 {
     fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if radix == 16 && s.contains("p") {
+            return Ok(libm::support::hf16(s));
+        }
+
         let s = strip_radix_prefix(s, radix);
         u16::from_str_radix(s, radix).map(Self::from_bits)
     }
@@ -334,6 +338,9 @@ impl FromStrRadix for f64 {
 #[cfg(f128_enabled)]
 impl FromStrRadix for f128 {
     fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
+        if radix == 16 && s.contains("p") {
+            return Ok(libm::support::hf128(s));
+        }
         let s = strip_radix_prefix(s, radix);
         u128::from_str_radix(s, radix).map(Self::from_bits)
     }
diff --git a/library/compiler-builtins/libm/src/math/support/hex_float.rs b/library/compiler-builtins/libm/src/math/support/hex_float.rs
index 949f21a57ff..da41622f296 100644
--- a/library/compiler-builtins/libm/src/math/support/hex_float.rs
+++ b/library/compiler-builtins/libm/src/math/support/hex_float.rs
@@ -2,7 +2,9 @@
 
 #![allow(dead_code)] // FIXME: remove once this gets used
 
-use super::{f32_from_bits, f64_from_bits};
+use core::fmt;
+
+use super::{Float, f32_from_bits, f64_from_bits};
 
 /// Construct a 16-bit float from hex float representation (C-style)
 #[cfg(f16_enabled)]
@@ -26,17 +28,25 @@ pub const fn hf128(s: &str) -> f128 {
     f128::from_bits(parse_any(s, 128, 112))
 }
 
-const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
+/// Parse any float from hex to its bitwise representation.
+///
+/// `nan_repr` is passed rather than constructed so the platform-specific NaN is returned.
+pub const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
     let exp_bits: u32 = bits - sig_bits - 1;
     let max_msb: i32 = (1 << (exp_bits - 1)) - 1;
     // The exponent of one ULP in the subnormals
     let min_lsb: i32 = 1 - max_msb - sig_bits as i32;
 
-    let (neg, mut sig, exp) = parse_hex(s.as_bytes());
+    let exp_mask = ((1 << exp_bits) - 1) << sig_bits;
 
-    if sig == 0 {
-        return (neg as u128) << (bits - 1);
-    }
+    let (neg, mut sig, exp) = match parse_hex(s.as_bytes()) {
+        Parsed::Finite { neg, sig: 0, .. } => return (neg as u128) << (bits - 1),
+        Parsed::Finite { neg, sig, exp } => (neg, sig, exp),
+        Parsed::Infinite { neg } => return ((neg as u128) << (bits - 1)) | exp_mask,
+        Parsed::Nan { neg } => {
+            return ((neg as u128) << (bits - 1)) | exp_mask | (1 << (sig_bits - 1));
+        }
+    };
 
     // exponents of the least and most significant bits in the value
     let lsb = sig.trailing_zeros() as i32;
@@ -76,11 +86,24 @@ const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
     sig | ((neg as u128) << (bits - 1))
 }
 
+/// A parsed floating point number.
+enum Parsed {
+    /// Absolute value sig * 2^e
+    Finite {
+        neg: bool,
+        sig: u128,
+        exp: i32,
+    },
+    Infinite {
+        neg: bool,
+    },
+    Nan {
+        neg: bool,
+    },
+}
+
 /// Parse a hexadecimal float x
-/// returns (s,n,e):
-///     s == x.is_sign_negative()
-///     n * 2^e == x.abs()
-const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
+const fn parse_hex(mut b: &[u8]) -> Parsed {
     let mut neg = false;
     let mut sig: u128 = 0;
     let mut exp: i32 = 0;
@@ -90,6 +113,12 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
         neg = c == b'-';
     }
 
+    match *b {
+        [b'i' | b'I', b'n' | b'N', b'f' | b'F'] => return Parsed::Infinite { neg },
+        [b'n' | b'N', b'a' | b'A', b'n' | b'N'] => return Parsed::Nan { neg },
+        _ => (),
+    }
+
     if let &[b'0', b'x' | b'X', ref rest @ ..] = b {
         b = rest;
     } else {
@@ -152,7 +181,7 @@ const fn parse_hex(mut b: &[u8]) -> (bool, u128, i32) {
         exp += pexp;
     }
 
-    (neg, sig, exp)
+    Parsed::Finite { neg, sig, exp }
 }
 
 const fn dec_digit(c: u8) -> u8 {
@@ -179,8 +208,107 @@ const fn u128_ilog2(v: u128) -> u32 {
     u128::BITS - 1 - v.leading_zeros()
 }
 
+/// Format a floating point number as its IEEE hex (`%a`) representation.
+pub struct Hexf<F>(pub F);
+
+// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs
+fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    if x.is_sign_negative() {
+        write!(f, "-")?;
+    }
+
+    if x.is_nan() {
+        return write!(f, "NaN");
+    } else if x.is_infinite() {
+        return write!(f, "inf");
+    } else if *x == F::ZERO {
+        return write!(f, "0x0p+0");
+    }
+
+    let mut exponent = x.exp_unbiased();
+    let sig = x.to_bits() & F::SIG_MASK;
+
+    let bias = F::EXP_BIAS as i32;
+    // The mantissa MSB needs to be shifted up to the nearest nibble.
+    let mshift = (4 - (F::SIG_BITS % 4)) % 4;
+    let sig = sig << mshift;
+    // The width is rounded up to the nearest char (4 bits)
+    let mwidth = (F::SIG_BITS as usize + 3) / 4;
+    let leading = if exponent == -bias {
+        // subnormal number means we shift our output by 1 bit.
+        exponent += 1;
+        "0."
+    } else {
+        "1."
+    };
+
+    write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}")
+}
+
+#[cfg(f16_enabled)]
+impl fmt::LowerHex for Hexf<f16> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt_any_hex(&self.0, f)
+    }
+}
+
+impl fmt::LowerHex for Hexf<f32> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt_any_hex(&self.0, f)
+    }
+}
+
+impl fmt::LowerHex for Hexf<f64> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt_any_hex(&self.0, f)
+    }
+}
+
+#[cfg(f128_enabled)]
+impl fmt::LowerHex for Hexf<f128> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt_any_hex(&self.0, f)
+    }
+}
+
+impl fmt::LowerHex for Hexf<i32> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::LowerHex::fmt(&self.0, f)
+    }
+}
+
+impl<T1, T2> fmt::LowerHex for Hexf<(T1, T2)>
+where
+    T1: Copy,
+    T2: Copy,
+    Hexf<T1>: fmt::LowerHex,
+    Hexf<T2>: fmt::LowerHex,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
+    }
+}
+
+impl<T> fmt::Debug for Hexf<T>
+where
+    Hexf<T>: fmt::LowerHex,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
+impl<T> fmt::Display for Hexf<T>
+where
+    Hexf<T>: fmt::LowerHex,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::LowerHex::fmt(self, f)
+    }
+}
+
 #[cfg(test)]
-mod tests {
+mod parse_tests {
     extern crate std;
     use std::{format, println};
 
@@ -272,6 +400,10 @@ mod tests {
                     ("-0x1.998p-4", (-0.1f16).to_bits()),
                     ("0x0.123p-12", 0x0123),
                     ("0x1p-24", 0x0001),
+                    ("nan", f16::NAN.to_bits()),
+                    ("-nan", (-f16::NAN).to_bits()),
+                    ("inf", f16::INFINITY.to_bits()),
+                    ("-inf", f16::NEG_INFINITY.to_bits()),
                 ];
                 for (s, exp) in checks {
                     println!("parsing {s}");
@@ -322,6 +454,10 @@ mod tests {
             ("0x1.111114p-127", 0x00444445),
             ("0x1.23456p-130", 0x00091a2b),
             ("0x1p-149", 0x00000001),
+            ("nan", f32::NAN.to_bits()),
+            ("-nan", (-f32::NAN).to_bits()),
+            ("inf", f32::INFINITY.to_bits()),
+            ("-inf", f32::NEG_INFINITY.to_bits()),
         ];
         for (s, exp) in checks {
             println!("parsing {s}");
@@ -360,6 +496,10 @@ mod tests {
             ("0x0.8000000000001p-1022", 0x0008000000000001),
             ("0x0.123456789abcdp-1022", 0x000123456789abcd),
             ("0x0.0000000000002p-1022", 0x0000000000000002),
+            ("nan", f64::NAN.to_bits()),
+            ("-nan", (-f64::NAN).to_bits()),
+            ("inf", f64::INFINITY.to_bits()),
+            ("-inf", f64::NEG_INFINITY.to_bits()),
         ];
         for (s, exp) in checks {
             println!("parsing {s}");
@@ -401,6 +541,10 @@ mod tests {
                     ("-0x1.999999999999999999999999999ap-4", (-0.1f128).to_bits()),
                     ("0x0.abcdef0123456789abcdef012345p-16382", 0x0000abcdef0123456789abcdef012345),
                     ("0x1p-16494", 0x00000000000000000000000000000001),
+                    ("nan", f128::NAN.to_bits()),
+                    ("-nan", (-f128::NAN).to_bits()),
+                    ("inf", f128::INFINITY.to_bits()),
+                    ("-inf", f128::NEG_INFINITY.to_bits()),
                 ];
                 for (s, exp) in checks {
                     println!("parsing {s}");
@@ -623,3 +767,79 @@ mod tests_panicking {
     #[cfg(f128_enabled)]
     f128_tests!();
 }
+
+#[cfg(test)]
+mod print_tests {
+    extern crate std;
+    use std::string::ToString;
+
+    use super::*;
+
+    #[test]
+    #[cfg(f16_enabled)]
+    fn test_f16() {
+        use std::format;
+        // Exhaustively check that `f16` roundtrips.
+        for x in 0..=u16::MAX {
+            let f = f16::from_bits(x);
+            let s = format!("{}", Hexf(f));
+            let from_s = hf16(&s);
+
+            if f.is_nan() && from_s.is_nan() {
+                continue;
+            }
+
+            assert_eq!(
+                f.to_bits(),
+                from_s.to_bits(),
+                "{f:?} formatted as {s} but parsed as {from_s:?}"
+            );
+        }
+    }
+
+    #[test]
+    fn spot_checks() {
+        assert_eq!(Hexf(f32::MAX).to_string(), "0x1.fffffep+127");
+        assert_eq!(Hexf(f64::MAX).to_string(), "0x1.fffffffffffffp+1023");
+
+        assert_eq!(Hexf(f32::MIN).to_string(), "-0x1.fffffep+127");
+        assert_eq!(Hexf(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023");
+
+        assert_eq!(Hexf(f32::ZERO).to_string(), "0x0p+0");
+        assert_eq!(Hexf(f64::ZERO).to_string(), "0x0p+0");
+
+        assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0");
+        assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0");
+
+        assert_eq!(Hexf(f32::NAN).to_string(), "NaN");
+        assert_eq!(Hexf(f64::NAN).to_string(), "NaN");
+
+        assert_eq!(Hexf(f32::INFINITY).to_string(), "inf");
+        assert_eq!(Hexf(f64::INFINITY).to_string(), "inf");
+
+        assert_eq!(Hexf(f32::NEG_INFINITY).to_string(), "-inf");
+        assert_eq!(Hexf(f64::NEG_INFINITY).to_string(), "-inf");
+
+        #[cfg(f16_enabled)]
+        {
+            assert_eq!(Hexf(f16::MAX).to_string(), "0x1.ffcp+15");
+            assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15");
+            assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0");
+            assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0");
+            assert_eq!(Hexf(f16::NAN).to_string(), "NaN");
+            assert_eq!(Hexf(f16::INFINITY).to_string(), "inf");
+            assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf");
+        }
+
+        #[cfg(f128_enabled)]
+        {
+            assert_eq!(Hexf(f128::MAX).to_string(), "0x1.ffffffffffffffffffffffffffffp+16383");
+            assert_eq!(Hexf(f128::MIN).to_string(), "-0x1.ffffffffffffffffffffffffffffp+16383");
+            assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0");
+            assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0");
+            assert_eq!(Hexf(f128::NAN).to_string(), "NaN");
+            assert_eq!(Hexf(f128::INFINITY).to_string(), "inf");
+            assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf");
+        }
+    }
+}
diff --git a/library/compiler-builtins/libm/src/math/support/mod.rs b/library/compiler-builtins/libm/src/math/support/mod.rs
index da9e2c9ed62..d471c5b7013 100644
--- a/library/compiler-builtins/libm/src/math/support/mod.rs
+++ b/library/compiler-builtins/libm/src/math/support/mod.rs
@@ -2,7 +2,7 @@
 pub mod macros;
 mod big;
 mod float_traits;
-mod hex_float;
+pub mod hex_float;
 mod int_traits;
 
 #[allow(unused_imports)]
@@ -13,7 +13,7 @@ pub use hex_float::hf16;
 #[cfg(f128_enabled)]
 pub use hex_float::hf128;
 #[allow(unused_imports)]
-pub use hex_float::{hf32, hf64};
+pub use hex_float::{Hexf, hf32, hf64};
 pub use int_traits::{CastFrom, CastInto, DInt, HInt, Int, MinInt};
 
 /// Hint to the compiler that the current path is cold.