| //! Utilities for working with hex float formats. | |
| pub use hex_fmt::{DisplayHex, Hex}; | |
| use super::{Round, Status, f32_from_bits, f64_from_bits}; | |
| /// Construct a 16-bit float from hex float representation (C-style) | |
| #[cfg(f16_enabled)] | |
| pub const fn hf16(s: &str) -> f16 { | |
| match parse_hex_exact(s, 16, 10) { | |
| Ok(bits) => f16::from_bits(bits as u16), | |
| Err(HexFloatParseError(s)) => panic!("{}", s), | |
| } | |
| } | |
| /// Construct a 32-bit float from hex float representation (C-style) | |
| #[allow(unused)] | |
| pub const fn hf32(s: &str) -> f32 { | |
| match parse_hex_exact(s, 32, 23) { | |
| Ok(bits) => f32_from_bits(bits as u32), | |
| Err(HexFloatParseError(s)) => panic!("{}", s), | |
| } | |
| } | |
| /// Construct a 64-bit float from hex float representation (C-style) | |
| pub const fn hf64(s: &str) -> f64 { | |
| match parse_hex_exact(s, 64, 52) { | |
| Ok(bits) => f64_from_bits(bits as u64), | |
| Err(HexFloatParseError(s)) => panic!("{}", s), | |
| } | |
| } | |
| /// Construct a 128-bit float from hex float representation (C-style) | |
| #[cfg(f128_enabled)] | |
| pub const fn hf128(s: &str) -> f128 { | |
| match parse_hex_exact(s, 128, 112) { | |
| Ok(bits) => f128::from_bits(bits), | |
| Err(HexFloatParseError(s)) => panic!("{}", s), | |
| } | |
| } | |
| #[derive(Copy, Clone, Debug)] | |
| pub struct HexFloatParseError(&'static str); | |
| /// Parses any float to its bitwise representation, returning an error if it cannot be represented exactly | |
| pub const fn parse_hex_exact( | |
| s: &str, | |
| bits: u32, | |
| sig_bits: u32, | |
| ) -> Result<u128, HexFloatParseError> { | |
| match parse_any(s, bits, sig_bits, Round::Nearest) { | |
| Err(e) => Err(e), | |
| Ok((bits, Status::OK)) => Ok(bits), | |
| Ok((_, status)) if status.overflow() => Err(HexFloatParseError("the value is too huge")), | |
| Ok((_, status)) if status.underflow() => Err(HexFloatParseError("the value is too tiny")), | |
| Ok((_, status)) if status.inexact() => Err(HexFloatParseError("the value is too precise")), | |
| Ok(_) => unreachable!(), | |
| } | |
| } | |
| /// Parse any float from hex to its bitwise representation. | |
| pub const fn parse_any( | |
| s: &str, | |
| bits: u32, | |
| sig_bits: u32, | |
| round: Round, | |
| ) -> Result<(u128, Status), HexFloatParseError> { | |
| let mut b = s.as_bytes(); | |
| if sig_bits > 119 || bits > 128 || bits < sig_bits + 3 || bits > sig_bits + 30 { | |
| return Err(HexFloatParseError("unsupported target float configuration")); | |
| } | |
| let neg = matches!(b, [b'-', ..]); | |
| if let &[b'-' | b'+', ref rest @ ..] = b { | |
| b = rest; | |
| } | |
| let sign_bit = 1 << (bits - 1); | |
| let quiet_bit = 1 << (sig_bits - 1); | |
| let nan = sign_bit - quiet_bit; | |
| let inf = nan - quiet_bit; | |
| let (mut x, status) = match *b { | |
| [b'i' | b'I', b'n' | b'N', b'f' | b'F'] => (inf, Status::OK), | |
| [b'n' | b'N', b'a' | b'A', b'n' | b'N'] => (nan, Status::OK), | |
| [b'0', b'x' | b'X', ref rest @ ..] => { | |
| let round = match (neg, round) { | |
| // parse("-x", Round::Positive) == -parse("x", Round::Negative) | |
| (true, Round::Positive) => Round::Negative, | |
| (true, Round::Negative) => Round::Positive, | |
| // rounding toward nearest or zero are symmetric | |
| (true, Round::Nearest | Round::Zero) | (false, _) => round, | |
| }; | |
| match parse_finite(rest, bits, sig_bits, round) { | |
| Err(e) => return Err(e), | |
| Ok(res) => res, | |
| } | |
| } | |
| _ => return Err(HexFloatParseError("no hex indicator")), | |
| }; | |
| if neg { | |
| x ^= sign_bit; | |
| } | |
| Ok((x, status)) | |
| } | |
| const fn parse_finite( | |
| b: &[u8], | |
| bits: u32, | |
| sig_bits: u32, | |
| rounding_mode: Round, | |
| ) -> Result<(u128, Status), HexFloatParseError> { | |
| 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 (mut sig, mut exp) = match parse_hex(b) { | |
| Err(e) => return Err(e), | |
| Ok(Parsed { sig: 0, .. }) => return Ok((0, Status::OK)), | |
| Ok(Parsed { sig, exp }) => (sig, exp), | |
| }; | |
| let mut round_bits = sig.ilog2() as i32 - sig_bits as i32; | |
| // Round at least up to min_lsb | |
| if exp < min_lsb - round_bits { | |
| round_bits = min_lsb - exp; | |
| } | |
| let mut status = Status::OK; | |
| exp += round_bits; | |
| if round_bits > 0 { | |
| // first, prepare for rounding exactly two bits | |
| if round_bits == 1 { | |
| sig <<= 1; | |
| } else if round_bits > 2 { | |
| sig = shr_odd_rounding(sig, (round_bits - 2) as u32); | |
| } | |
| if sig & 0b11 != 0 { | |
| status = Status::INEXACT; | |
| } | |
| sig = shr2_round(sig, rounding_mode); | |
| } else if round_bits < 0 { | |
| sig <<= -round_bits; | |
| } | |
| // The parsed value is X = sig * 2^exp | |
| // Expressed as a multiple U of the smallest subnormal value: | |
| // X = U * 2^min_lsb, so U = sig * 2^(exp-min_lsb) | |
| let uexp = (exp - min_lsb) as u128; | |
| let uexp = uexp << sig_bits; | |
| // Note that it is possible for the exponent bits to equal 2 here | |
| // if the value rounded up, but that means the mantissa is all zeroes | |
| // so the value is still correct | |
| debug_assert!(sig <= 2 << sig_bits); | |
| let inf = ((1 << exp_bits) - 1) << sig_bits; | |
| let bits = match sig.checked_add(uexp) { | |
| Some(bits) if bits < inf => { | |
| // inexact subnormal or zero? | |
| if status.inexact() && bits < (1 << sig_bits) { | |
| status = status.with(Status::UNDERFLOW); | |
| } | |
| bits | |
| } | |
| _ => { | |
| // overflow to infinity | |
| status = status.with(Status::OVERFLOW).with(Status::INEXACT); | |
| match rounding_mode { | |
| Round::Positive | Round::Nearest => inf, | |
| Round::Negative | Round::Zero => inf - 1, | |
| } | |
| } | |
| }; | |
| Ok((bits, status)) | |
| } | |
| /// Shift right, rounding all inexact divisions to the nearest odd number | |
| /// E.g. (0 >> 4) -> 0, (1..=31 >> 4) -> 1, (32 >> 4) -> 2, ... | |
| /// | |
| /// Useful for reducing a number before rounding the last two bits, since | |
| /// the result of the final rounding is preserved for all rounding modes. | |
| const fn shr_odd_rounding(x: u128, k: u32) -> u128 { | |
| if k < 128 { | |
| let inexact = x.trailing_zeros() < k; | |
| (x >> k) | (inexact as u128) | |
| } else { | |
| (x != 0) as u128 | |
| } | |
| } | |
| /// Divide by 4, rounding with the given mode | |
| const fn shr2_round(mut x: u128, round: Round) -> u128 { | |
| let t = (x as u32) & 0b111; | |
| x >>= 2; | |
| match round { | |
| // Look-up-table on the last three bits for when to round up | |
| Round::Nearest => x + ((0b11001000_u8 >> t) & 1) as u128, | |
| Round::Negative => x, | |
| Round::Zero => x, | |
| Round::Positive => x + (t & 0b11 != 0) as u128, | |
| } | |
| } | |
| /// A parsed finite and unsigned floating point number. | |
| struct Parsed { | |
| /// Absolute value sig * 2^exp | |
| sig: u128, | |
| exp: i32, | |
| } | |
| /// Parse a hexadecimal float x | |
| const fn parse_hex(mut b: &[u8]) -> Result<Parsed, HexFloatParseError> { | |
| let mut sig: u128 = 0; | |
| let mut exp: i32 = 0; | |
| let mut seen_point = false; | |
| let mut some_digits = false; | |
| let mut inexact = false; | |
| while let &[c, ref rest @ ..] = b { | |
| b = rest; | |
| match c { | |
| b'.' => { | |
| if seen_point { | |
| return Err(HexFloatParseError( | |
| "unexpected '.' parsing fractional digits", | |
| )); | |
| } | |
| seen_point = true; | |
| continue; | |
| } | |
| b'p' | b'P' => break, | |
| c => { | |
| let digit = match hex_digit(c) { | |
| Some(d) => d, | |
| None => return Err(HexFloatParseError("expected hexadecimal digit")), | |
| }; | |
| some_digits = true; | |
| if (sig >> 124) == 0 { | |
| sig <<= 4; | |
| sig |= digit as u128; | |
| } else { | |
| // FIXME: it is technically possible for exp to overflow if parsing a string with >500M digits | |
| exp += 4; | |
| inexact |= digit != 0; | |
| } | |
| // Up until the fractional point, the value grows | |
| // with more digits, but after it the exponent is | |
| // compensated to match. | |
| if seen_point { | |
| exp -= 4; | |
| } | |
| } | |
| } | |
| } | |
| // If we've set inexact, the exact value has more than 125 | |
| // significant bits, and lies somewhere between sig and sig + 1. | |
| // Because we'll round off at least two of the trailing bits, | |
| // setting the last bit gives correct rounding for inexact values. | |
| sig |= inexact as u128; | |
| if !some_digits { | |
| return Err(HexFloatParseError("at least one digit is required")); | |
| }; | |
| some_digits = false; | |
| let negate_exp = matches!(b, [b'-', ..]); | |
| if let &[b'-' | b'+', ref rest @ ..] = b { | |
| b = rest; | |
| } | |
| let mut pexp: u32 = 0; | |
| while let &[c, ref rest @ ..] = b { | |
| b = rest; | |
| let digit = match dec_digit(c) { | |
| Some(d) => d, | |
| None => return Err(HexFloatParseError("expected decimal digit")), | |
| }; | |
| some_digits = true; | |
| pexp = pexp.saturating_mul(10); | |
| pexp += digit as u32; | |
| } | |
| if !some_digits { | |
| return Err(HexFloatParseError( | |
| "at least one exponent digit is required", | |
| )); | |
| }; | |
| if negate_exp { | |
| exp = exp.saturating_sub_unsigned(pexp); | |
| } else { | |
| exp = exp.saturating_add_unsigned(pexp); | |
| }; | |
| Ok(Parsed { sig, exp }) | |
| } | |
| const fn dec_digit(c: u8) -> Option<u8> { | |
| match c { | |
| b'0'..=b'9' => Some(c - b'0'), | |
| _ => None, | |
| } | |
| } | |
| const fn hex_digit(c: u8) -> Option<u8> { | |
| match c { | |
| b'0'..=b'9' => Some(c - b'0'), | |
| b'a'..=b'f' => Some(c - b'a' + 10), | |
| b'A'..=b'F' => Some(c - b'A' + 10), | |
| _ => None, | |
| } | |
| } | |
| mod hex_fmt { | |
| use core::fmt; | |
| use crate::support::{Float, div_ceil_u32}; | |
| // Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs | |
| pub(super) fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| if x.is_sign_negative() { | |
| write!(f, "-")?; | |
| } else { | |
| write!(f, "+")?; | |
| } | |
| if x.is_nan() { | |
| if x.is_snan() { | |
| write!(f, "sNaN")?; | |
| } else if x.is_nan() { | |
| write!(f, "qNaN")?; | |
| } | |
| let payload = x.frac() & !F::SIG_TOP_BIT; | |
| let width = div_ceil_u32(F::SIG_BITS, 4) as usize + 2; | |
| write!(f, "({payload:#0width$x})")?; | |
| return Ok(()); | |
| } | |
| 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:+}") | |
| } | |
| /// Types that can be formatted as hex via `Hex`. For ints we always print with a fixed | |
| /// number of leading zeros. For floats we use the IEEE hex (`%a`) representation. The `-` | |
| /// format modifier is used to print the integer hex representation rather than hex float. | |
| pub trait DisplayHex { | |
| #[allow(unused)] // Only used for tests and public test internals | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result; | |
| } | |
| /// A wrapper implementing formatting traits via `DisplayHex`. | |
| #[allow(unused)] // Only used for tests and public test internals | |
| pub struct Hex<T>(pub T); | |
| impl<T: DisplayHex> fmt::Debug for Hex<T> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| self.0.fmt(f) | |
| } | |
| } | |
| impl<T: DisplayHex> fmt::Display for Hex<T> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| self.0.fmt(f) | |
| } | |
| } | |
| impl<T: DisplayHex> fmt::LowerHex for Hex<T> { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| self.0.fmt(f) | |
| } | |
| } | |
| impl<F: Float> DisplayHex for F { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| if f.sign_minus() { | |
| self.to_bits().fmt(f) | |
| } else { | |
| fmt_any_hex(self, f) | |
| } | |
| } | |
| } | |
| macro_rules! impl_int { | |
| ($ity:ty) => { | |
| impl DisplayHex for $ity { | |
| #[inline] | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!( | |
| f, | |
| "{self:#0width$x}", | |
| width = ((<$ity>::BITS / 4) + 2) as usize, | |
| ) | |
| } | |
| } | |
| }; | |
| } | |
| impl_int!(i8); | |
| impl_int!(i16); | |
| impl_int!(i32); | |
| impl_int!(i64); | |
| impl_int!(i128); | |
| impl_int!(isize); | |
| impl_int!(u8); | |
| impl_int!(u16); | |
| impl_int!(u32); | |
| impl_int!(u64); | |
| impl_int!(u128); | |
| impl_int!(usize); | |
| // Not really a meaningful impl, but makes some generics easier. | |
| impl DisplayHex for bool { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| cfg_if! { | |
| if #[cfg(feature = "compiler-builtins")] { | |
| let _ = f; | |
| unimplemented!() | |
| } else { | |
| write!(f, "{self}") | |
| } | |
| } | |
| } | |
| } | |
| impl<T1> DisplayHex for (T1,) | |
| where | |
| T1: Copy + DisplayHex, | |
| { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "(")?; | |
| self.0.fmt(f)?; | |
| write!(f, ",)") | |
| } | |
| } | |
| impl<T1, T2> DisplayHex for (T1, T2) | |
| where | |
| T1: Copy + DisplayHex, | |
| T2: Copy + DisplayHex, | |
| { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "(")?; | |
| self.0.fmt(f)?; | |
| write!(f, ", ")?; | |
| self.1.fmt(f)?; | |
| write!(f, ")") | |
| } | |
| } | |
| impl<T1, T2, T3> DisplayHex for (T1, T2, T3) | |
| where | |
| T1: Copy + DisplayHex, | |
| T2: Copy + DisplayHex, | |
| T3: Copy + DisplayHex, | |
| { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "(")?; | |
| self.0.fmt(f)?; | |
| write!(f, ", ")?; | |
| self.1.fmt(f)?; | |
| write!(f, ", ")?; | |
| self.2.fmt(f)?; | |
| write!(f, ")") | |
| } | |
| } | |
| } | |
| #[cfg(test)] | |
| mod parse_tests { | |
| extern crate std; | |
| use std::string::{String, ToString}; | |
| use std::{format, println}; | |
| use super::*; | |
| #[cfg(f16_enabled)] | |
| fn rounding_properties(s: &str) -> Result<(), HexFloatParseError> { | |
| let (xd, s0) = parse_any(s, 16, 10, Round::Negative)?; | |
| let (xu, s1) = parse_any(s, 16, 10, Round::Positive)?; | |
| let (xz, s2) = parse_any(s, 16, 10, Round::Zero)?; | |
| let (xn, s3) = parse_any(s, 16, 10, Round::Nearest)?; | |
| // FIXME: A value between the least normal and largest subnormal | |
| // could have underflow status depend on rounding mode. | |
| if let Status::OK = s0 { | |
| // an exact result is the same for all rounding modes | |
| assert_eq!(s0, s1); | |
| assert_eq!(s0, s2); | |
| assert_eq!(s0, s3); | |
| assert_eq!(xd, xu); | |
| assert_eq!(xd, xz); | |
| assert_eq!(xd, xn); | |
| } else { | |
| assert!([s0, s1, s2, s3].into_iter().all(Status::inexact)); | |
| let xd = f16::from_bits(xd as u16); | |
| let xu = f16::from_bits(xu as u16); | |
| let xz = f16::from_bits(xz as u16); | |
| let xn = f16::from_bits(xn as u16); | |
| assert_biteq!(xd.next_up(), xu, "s={s}, xd={xd:?}, xu={xu:?}"); | |
| let signs = [xd, xu, xz, xn].map(f16::is_sign_negative); | |
| if signs == [true; 4] { | |
| assert_biteq!(xz, xu); | |
| } else { | |
| assert_eq!(signs, [false; 4]); | |
| assert_biteq!(xz, xd); | |
| } | |
| if xn.to_bits() != xd.to_bits() { | |
| assert_biteq!(xn, xu); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| /// Strip the qNaN/sNaN and payload since we don't parse that. | |
| #[cfg_attr(not(f16_enabled), expect(unused))] | |
| pub fn canonicalize_snan_str(mut s: String) -> String { | |
| if s.contains("sNaN") || s.contains("qNaN") { | |
| s = s.replace("sNaN", "NaN").replace("qNaN", "NaN"); | |
| if let Some((nan, payload)) = s.split_once("(") { | |
| assert!(payload.ends_with(")")); | |
| s = nan.to_string(); | |
| } | |
| } | |
| s | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| fn test_rounding() { | |
| let n = 1_i32 << 14; | |
| for i in -n..n { | |
| let u = i.rotate_right(11) as u32; | |
| let s = format!("{}", Hex(f32::from_bits(u))); | |
| let s = canonicalize_snan_str(s); | |
| match rounding_properties(&s) { | |
| Ok(()) => (), | |
| Err(e) => panic!("failed rounding properties for `{s}`: {e:?}"), | |
| } | |
| } | |
| } | |
| #[test] | |
| fn test_parse_any() { | |
| for k in -149..=127 { | |
| let s = format!("0x1p{k}"); | |
| let x = hf32(&s); | |
| let y = if k < 0 { | |
| 0.5f32.powi(-k) | |
| } else { | |
| 2.0f32.powi(k) | |
| }; | |
| assert_eq!(x, y); | |
| } | |
| let mut s = *b"0x.0000000p-121"; | |
| for e in 0..40 { | |
| for k in 0..(1 << 15) { | |
| let expected = f32::from_bits(k) * 2.0f32.powi(e); | |
| let x = hf32(std::str::from_utf8(&s).unwrap()); | |
| assert_eq!( | |
| x.to_bits(), | |
| expected.to_bits(), | |
| "\ | |
| e={e}\n\ | |
| k={k}\n\ | |
| x={x}\n\ | |
| expected={expected}\n\ | |
| s={}\n\ | |
| f32::from_bits(k)={}\n\ | |
| 2.0f32.powi(e)={}\ | |
| ", | |
| std::str::from_utf8(&s).unwrap(), | |
| f32::from_bits(k), | |
| 2.0f32.powi(e), | |
| ); | |
| for i in (3..10).rev() { | |
| if s[i] == b'f' { | |
| s[i] = b'0'; | |
| } else if s[i] == b'9' { | |
| s[i] = b'a'; | |
| break; | |
| } else { | |
| s[i] += 1; | |
| break; | |
| } | |
| } | |
| } | |
| for i in (12..15).rev() { | |
| if s[i] == b'0' { | |
| s[i] = b'9'; | |
| } else { | |
| s[i] -= 1; | |
| break; | |
| } | |
| } | |
| for i in (3..10).rev() { | |
| s[i] = b'0'; | |
| } | |
| } | |
| } | |
| // FIXME: this test is causing failures that are likely UB on various platforms | |
| #[cfg(all(target_arch = "x86_64", target_os = "linux"))] | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| fn rounding() { | |
| let pi = std::f128::consts::PI; | |
| let s = format!("{}", Hex(pi)); | |
| for k in 0..=111 { | |
| let (bits, status) = parse_any(&s, 128 - k, 112 - k, Round::Nearest).unwrap(); | |
| let scale = (1u128 << (112 - k - 1)) as f128; | |
| let expected = (pi * scale).round_ties_even() / scale; | |
| assert_eq!(bits << k, expected.to_bits(), "k = {k}, s = {s}"); | |
| assert_eq!(expected != pi, status.inexact()); | |
| } | |
| } | |
| #[test] | |
| fn rounding_extreme_underflow() { | |
| for k in 1..1000 { | |
| let s = format!("0x1p{}", -149 - k); | |
| let Ok((bits, status)) = parse_any(&s, 32, 23, Round::Nearest) else { | |
| unreachable!() | |
| }; | |
| assert_eq!(bits, 0, "{s} should round to zero, got bits={bits}"); | |
| assert!( | |
| status.underflow(), | |
| "should indicate underflow when parsing {s}" | |
| ); | |
| assert!(status.inexact(), "should indicate inexact when parsing {s}"); | |
| } | |
| } | |
| #[test] | |
| fn long_tail() { | |
| for k in 1..1000 { | |
| let s = format!("0x1.{}p0", "0".repeat(k)); | |
| let Ok(bits) = parse_hex_exact(&s, 32, 23) else { | |
| panic!("parsing {s} failed") | |
| }; | |
| assert_eq!(f32::from_bits(bits as u32), 1.0); | |
| let s = format!("0x1.{}1p0", "0".repeat(k)); | |
| let Ok((bits, status)) = parse_any(&s, 32, 23, Round::Nearest) else { | |
| unreachable!() | |
| }; | |
| if status.inexact() { | |
| assert!(1.0 == f32::from_bits(bits as u32)); | |
| } else { | |
| assert!(1.0 < f32::from_bits(bits as u32)); | |
| } | |
| } | |
| } | |
| // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to | |
| // hide them from the AST. | |
| #[cfg(f16_enabled)] | |
| macro_rules! f16_tests { | |
| () => { | |
| #[test] | |
| fn test_f16() { | |
| let checks = [ | |
| ("0x.1234p+16", (0x1234 as f16).to_bits()), | |
| ("0x1.234p+12", (0x1234 as f16).to_bits()), | |
| ("0x12.34p+8", (0x1234 as f16).to_bits()), | |
| ("0x123.4p+4", (0x1234 as f16).to_bits()), | |
| ("0x1234p+0", (0x1234 as f16).to_bits()), | |
| ("0x1234.p+0", (0x1234 as f16).to_bits()), | |
| ("0x1234.0p+0", (0x1234 as f16).to_bits()), | |
| ("0x1.ffcp+15", f16::MAX.to_bits()), | |
| ("0x1.0p+1", 2.0f16.to_bits()), | |
| ("0x1.0p+0", 1.0f16.to_bits()), | |
| ("0x1.ffp+8", 0x5ffc), | |
| ("+0x1.ffp+8", 0x5ffc), | |
| ("0x1p+0", 0x3c00), | |
| ("0x1.998p-4", 0x2e66), | |
| ("0x1.9p+6", 0x5640), | |
| ("0x0.0p0", 0.0f16.to_bits()), | |
| ("-0x0.0p0", (-0.0f16).to_bits()), | |
| ("0x1.0p0", 1.0f16.to_bits()), | |
| ("0x1.998p-4", (0.1f16).to_bits()), | |
| ("-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}"); | |
| assert!(rounding_properties(s).is_ok()); | |
| let act = hf16(s).to_bits(); | |
| assert_eq!( | |
| act, exp, | |
| "parsing {s}: {act:#06x} != {exp:#06x}\nact: {act:#018b}\nexp: {exp:#018b}" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn test_macros_f16() { | |
| assert_eq!(hf16!("0x1.ffp+8").to_bits(), 0x5ffc_u16); | |
| } | |
| }; | |
| } | |
| #[cfg(f16_enabled)] | |
| f16_tests!(); | |
| #[test] | |
| fn test_f32() { | |
| let checks = [ | |
| ("0x.1234p+16", (0x1234 as f32).to_bits()), | |
| ("0x1.234p+12", (0x1234 as f32).to_bits()), | |
| ("0x12.34p+8", (0x1234 as f32).to_bits()), | |
| ("0x123.4p+4", (0x1234 as f32).to_bits()), | |
| ("0x1234p+0", (0x1234 as f32).to_bits()), | |
| ("0x1234.p+0", (0x1234 as f32).to_bits()), | |
| ("0x1234.0p+0", (0x1234 as f32).to_bits()), | |
| ("0x1.fffffep+127", f32::MAX.to_bits()), | |
| ("0x1.0p+1", 2.0f32.to_bits()), | |
| ("0x1.0p+0", 1.0f32.to_bits()), | |
| ("0x1.ffep+8", 0x43fff000), | |
| ("+0x1.ffep+8", 0x43fff000), | |
| ("0x1p+0", 0x3f800000), | |
| ("0x1.99999ap-4", 0x3dcccccd), | |
| ("0x1.9p+6", 0x42c80000), | |
| ("0x1.2d5ed2p+20", 0x4996af69), | |
| ("-0x1.348eb8p+10", 0xc49a475c), | |
| ("-0x1.33dcfep-33", 0xaf19ee7f), | |
| ("0x0.0p0", 0.0f32.to_bits()), | |
| ("-0x0.0p0", (-0.0f32).to_bits()), | |
| ("0x1.0p0", 1.0f32.to_bits()), | |
| ("0x1.99999ap-4", (0.1f32).to_bits()), | |
| ("-0x1.99999ap-4", (-0.1f32).to_bits()), | |
| ("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}"); | |
| let act = hf32(s).to_bits(); | |
| assert_eq!( | |
| act, exp, | |
| "parsing {s}: {act:#010x} != {exp:#010x}\nact: {act:#034b}\nexp: {exp:#034b}" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn test_f64() { | |
| let checks = [ | |
| ("0x.1234p+16", (0x1234 as f64).to_bits()), | |
| ("0x1.234p+12", (0x1234 as f64).to_bits()), | |
| ("0x12.34p+8", (0x1234 as f64).to_bits()), | |
| ("0x123.4p+4", (0x1234 as f64).to_bits()), | |
| ("0x1234p+0", (0x1234 as f64).to_bits()), | |
| ("0x1234.p+0", (0x1234 as f64).to_bits()), | |
| ("0x1234.0p+0", (0x1234 as f64).to_bits()), | |
| ("0x1.ffep+8", 0x407ffe0000000000), | |
| ("0x1p+0", 0x3ff0000000000000), | |
| ("0x1.999999999999ap-4", 0x3fb999999999999a), | |
| ("0x1.9p+6", 0x4059000000000000), | |
| ("0x1.2d5ed1fe1da7bp+20", 0x4132d5ed1fe1da7b), | |
| ("-0x1.348eb851eb852p+10", 0xc09348eb851eb852), | |
| ("-0x1.33dcfe54a3803p-33", 0xbde33dcfe54a3803), | |
| ("0x1.0p0", 1.0f64.to_bits()), | |
| ("0x0.0p0", 0.0f64.to_bits()), | |
| ("-0x0.0p0", (-0.0f64).to_bits()), | |
| ("0x1.999999999999ap-4", 0.1f64.to_bits()), | |
| ("0x1.999999999998ap-4", (0.1f64 - f64::EPSILON).to_bits()), | |
| ("-0x1.999999999999ap-4", (-0.1f64).to_bits()), | |
| ("-0x1.999999999998ap-4", (-0.1f64 + f64::EPSILON).to_bits()), | |
| ("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}"); | |
| let act = hf64(s).to_bits(); | |
| assert_eq!( | |
| act, exp, | |
| "parsing {s}: {act:#018x} != {exp:#018x}\nact: {act:#066b}\nexp: {exp:#066b}" | |
| ); | |
| } | |
| } | |
| // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to | |
| // hide them from the AST. | |
| #[cfg(f128_enabled)] | |
| macro_rules! f128_tests { | |
| () => { | |
| #[test] | |
| fn test_f128() { | |
| let checks = [ | |
| ("0x.1234p+16", (0x1234 as f128).to_bits()), | |
| ("0x1.234p+12", (0x1234 as f128).to_bits()), | |
| ("0x12.34p+8", (0x1234 as f128).to_bits()), | |
| ("0x123.4p+4", (0x1234 as f128).to_bits()), | |
| ("0x1234p+0", (0x1234 as f128).to_bits()), | |
| ("0x1234.p+0", (0x1234 as f128).to_bits()), | |
| ("0x1234.0p+0", (0x1234 as f128).to_bits()), | |
| ("0x1.ffffffffffffffffffffffffffffp+16383", f128::MAX.to_bits()), | |
| ("0x1.0p+1", 2.0f128.to_bits()), | |
| ("0x1.0p+0", 1.0f128.to_bits()), | |
| ("0x1.ffep+8", 0x4007ffe0000000000000000000000000), | |
| ("+0x1.ffep+8", 0x4007ffe0000000000000000000000000), | |
| ("0x1p+0", 0x3fff0000000000000000000000000000), | |
| ("0x1.999999999999999999999999999ap-4", 0x3ffb999999999999999999999999999a), | |
| ("0x1.9p+6", 0x40059000000000000000000000000000), | |
| ("0x0.0p0", 0.0f128.to_bits()), | |
| ("-0x0.0p0", (-0.0f128).to_bits()), | |
| ("0x1.0p0", 1.0f128.to_bits()), | |
| ("0x1.999999999999999999999999999ap-4", (0.1f128).to_bits()), | |
| ("-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}"); | |
| let act = hf128(s).to_bits(); | |
| assert_eq!( | |
| act, exp, | |
| "parsing {s}: {act:#034x} != {exp:#034x}\nact: {act:#0130b}\nexp: {exp:#0130b}" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn test_macros_f128() { | |
| assert_eq!(hf128!("0x1.ffep+8").to_bits(), 0x4007ffe0000000000000000000000000_u128); | |
| } | |
| } | |
| } | |
| #[cfg(f128_enabled)] | |
| f128_tests!(); | |
| #[test] | |
| fn test_macros() { | |
| #[cfg(f16_enabled)] | |
| assert_eq!(hf16!("0x1.ffp+8").to_bits(), 0x5ffc_u16); | |
| assert_eq!(hf32!("0x1.ffep+8").to_bits(), 0x43fff000_u32); | |
| assert_eq!(hf64!("0x1.ffep+8").to_bits(), 0x407ffe0000000000_u64); | |
| #[cfg(f128_enabled)] | |
| assert_eq!( | |
| hf128!("0x1.ffep+8").to_bits(), | |
| 0x4007ffe0000000000000000000000000_u128 | |
| ); | |
| } | |
| } | |
| #[cfg(test)] | |
| mod tests_panicking { | |
| extern crate std; | |
| use super::*; | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| fn test_f16_almost_extra_precision() { | |
| // Exact maximum precision allowed | |
| hf16("0x1.ffcp+0"); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| #[should_panic(expected = "the value is too precise")] | |
| fn test_f16_extra_precision() { | |
| // One bit more than the above. | |
| hf16("0x1.ffdp+0"); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| #[should_panic(expected = "the value is too huge")] | |
| fn test_f16_overflow() { | |
| // One bit more than the above. | |
| hf16("0x1p+16"); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| fn test_f16_tiniest() { | |
| let x = hf16("0x1.p-24"); | |
| let y = hf16("0x0.001p-12"); | |
| let z = hf16("0x0.8p-23"); | |
| assert_eq!(x, y); | |
| assert_eq!(x, z); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f16_too_tiny() { | |
| hf16("0x1.p-25"); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f16_also_too_tiny() { | |
| hf16("0x0.8p-24"); | |
| } | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f16_again_too_tiny() { | |
| hf16("0x0.001p-13"); | |
| } | |
| #[test] | |
| fn test_f32_almost_extra_precision() { | |
| // Exact maximum precision allowed | |
| hf32("0x1.abcdeep+0"); | |
| } | |
| #[test] | |
| #[should_panic] | |
| fn test_f32_extra_precision2() { | |
| // One bit more than the above. | |
| hf32("0x1.ffffffp+127"); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too huge")] | |
| fn test_f32_overflow() { | |
| // One bit more than the above. | |
| hf32("0x1p+128"); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too precise")] | |
| fn test_f32_extra_precision() { | |
| // One bit more than the above. | |
| hf32("0x1.abcdefp+0"); | |
| } | |
| #[test] | |
| fn test_f32_tiniest() { | |
| let x = hf32("0x1.p-149"); | |
| let y = hf32("0x0.0000000000000001p-85"); | |
| let z = hf32("0x0.8p-148"); | |
| assert_eq!(x, y); | |
| assert_eq!(x, z); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f32_too_tiny() { | |
| hf32("0x1.p-150"); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f32_also_too_tiny() { | |
| hf32("0x0.8p-149"); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f32_again_too_tiny() { | |
| hf32("0x0.0000000000000001p-86"); | |
| } | |
| #[test] | |
| fn test_f64_almost_extra_precision() { | |
| // Exact maximum precision allowed | |
| hf64("0x1.abcdabcdabcdfp+0"); | |
| } | |
| #[test] | |
| #[should_panic(expected = "the value is too precise")] | |
| fn test_f64_extra_precision() { | |
| // One bit more than the above. | |
| hf64("0x1.abcdabcdabcdf8p+0"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| fn test_f128_almost_extra_precision() { | |
| // Exact maximum precision allowed | |
| hf128("0x1.ffffffffffffffffffffffffffffp+16383"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too precise")] | |
| fn test_f128_extra_precision() { | |
| // Just below the maximum finite. | |
| hf128("0x1.fffffffffffffffffffffffffffe8p+16383"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too huge")] | |
| fn test_f128_extra_precision_overflow() { | |
| // One bit more than the above. Should overflow. | |
| hf128("0x1.ffffffffffffffffffffffffffff8p+16383"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too huge")] | |
| fn test_f128_overflow() { | |
| // One bit more than the above. | |
| hf128("0x1p+16384"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| fn test_f128_tiniest() { | |
| let x = hf128("0x1.p-16494"); | |
| let y = hf128("0x0.0000000000000001p-16430"); | |
| let z = hf128("0x0.8p-16493"); | |
| assert_eq!(x, y); | |
| assert_eq!(x, z); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f128_too_tiny() { | |
| hf128("0x1.p-16495"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f128_again_too_tiny() { | |
| hf128("0x0.0000000000000001p-16431"); | |
| } | |
| #[test] | |
| #[cfg(f128_enabled)] | |
| #[should_panic(expected = "the value is too tiny")] | |
| fn test_f128_also_too_tiny() { | |
| hf128("0x0.8p-16494"); | |
| } | |
| } | |
| #[cfg(test)] | |
| mod print_tests { | |
| extern crate std; | |
| use std::string::ToString; | |
| use super::*; | |
| use crate::support::Float; | |
| #[test] | |
| #[cfg(f16_enabled)] | |
| fn test_f16() { | |
| use std::format; | |
| // Exhaustively check that `f16` roundtrips. | |
| for x in 0..=u16::MAX { | |
| use super::parse_tests::canonicalize_snan_str; | |
| let f = f16::from_bits(x); | |
| let s = format!("{}", Hex(f)); | |
| let s = canonicalize_snan_str(s); | |
| 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] | |
| #[cfg(f16_enabled)] | |
| fn test_f16_to_f32() { | |
| use std::format; | |
| use super::parse_tests::canonicalize_snan_str; | |
| // Exhaustively check that these are equivalent for all `f16`: | |
| // - `f16 -> f32` | |
| // - `f16 -> str -> f32` | |
| // - `f16 -> f32 -> str -> f32` | |
| // - `f16 -> f32 -> str -> f16 -> f32` | |
| for x in 0..=u16::MAX { | |
| let f16 = f16::from_bits(x); | |
| let s16 = format!("{}", Hex(f16)); | |
| let s16 = canonicalize_snan_str(s16); | |
| let f32 = f16 as f32; | |
| let s32 = format!("{}", Hex(f32)); | |
| let s32 = canonicalize_snan_str(s32); | |
| let a = hf32(&s16); | |
| let b = hf32(&s32); | |
| let c = hf16(&s32); | |
| if f32.is_nan() && a.is_nan() && b.is_nan() && c.is_nan() { | |
| continue; | |
| } | |
| assert_eq!( | |
| f32.to_bits(), | |
| a.to_bits(), | |
| "{f16:?} : f16 formatted as {s16} which parsed as {a:?} : f16" | |
| ); | |
| assert_eq!( | |
| f32.to_bits(), | |
| b.to_bits(), | |
| "{f32:?} : f32 formatted as {s32} which parsed as {b:?} : f32" | |
| ); | |
| assert_eq!( | |
| f32.to_bits(), | |
| (c as f32).to_bits(), | |
| "{f32:?} : f32 formatted as {s32} which parsed as {c:?} : f16" | |
| ); | |
| } | |
| } | |
| #[test] | |
| fn spot_checks() { | |
| assert_eq!(Hex(f32::MAX).to_string(), "+0x1.fffffep+127"); | |
| assert_eq!(Hex(f64::MAX).to_string(), "+0x1.fffffffffffffp+1023"); | |
| assert_eq!(Hex(f32::MIN).to_string(), "-0x1.fffffep+127"); | |
| assert_eq!(Hex(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023"); | |
| assert_eq!(Hex(f32::ZERO).to_string(), "+0x0p+0"); | |
| assert_eq!(Hex(f64::ZERO).to_string(), "+0x0p+0"); | |
| assert_eq!(Hex(f32::NEG_ZERO).to_string(), "-0x0p+0"); | |
| assert_eq!(Hex(f64::NEG_ZERO).to_string(), "-0x0p+0"); | |
| assert_eq!(Hex(f32::NAN).to_string(), "+qNaN(0x000000)"); | |
| assert_eq!(Hex(f64::NAN).to_string(), "+qNaN(0x0000000000000)"); | |
| assert_eq!(Hex(f32::NEG_NAN).to_string(), "-qNaN(0x000000)"); | |
| assert_eq!(Hex(f64::NEG_NAN).to_string(), "-qNaN(0x0000000000000)"); | |
| if !cfg!(x86_no_sse2) { | |
| // FIXME(rust-lang/rust#115567): calls quiet the sNaN | |
| assert_eq!(Hex(f32::SNAN).to_string(), "+sNaN(0x200000)"); | |
| assert_eq!(Hex(f64::SNAN).to_string(), "+sNaN(0x4000000000000)"); | |
| assert_eq!(Hex(f32::NEG_SNAN).to_string(), "-sNaN(0x200000)"); | |
| assert_eq!(Hex(f64::NEG_SNAN).to_string(), "-sNaN(0x4000000000000)"); | |
| assert_eq!(Hex(f32::from_bits(u32::MAX)).to_string(), "-qNaN(0x3fffff)"); | |
| assert_eq!( | |
| Hex(f64::from_bits(u64::MAX)).to_string(), | |
| "-qNaN(0x7ffffffffffff)" | |
| ); | |
| } | |
| assert_eq!(Hex(f32::INFINITY).to_string(), "+inf"); | |
| assert_eq!(Hex(f64::INFINITY).to_string(), "+inf"); | |
| assert_eq!(Hex(f32::NEG_INFINITY).to_string(), "-inf"); | |
| assert_eq!(Hex(f64::NEG_INFINITY).to_string(), "-inf"); | |
| #[cfg(f16_enabled)] | |
| { | |
| assert_eq!(Hex(f16::MAX).to_string(), "+0x1.ffcp+15"); | |
| assert_eq!(Hex(f16::MIN).to_string(), "-0x1.ffcp+15"); | |
| assert_eq!(Hex(f16::ZERO).to_string(), "+0x0p+0"); | |
| assert_eq!(Hex(f16::NEG_ZERO).to_string(), "-0x0p+0"); | |
| assert_eq!(Hex(f16::NAN).to_string(), "+qNaN(0x000)"); | |
| assert_eq!(Hex(f16::SNAN).to_string(), "+sNaN(0x100)"); | |
| assert_eq!(Hex(f16::NEG_NAN).to_string(), "-qNaN(0x000)"); | |
| assert_eq!(Hex(f16::NEG_SNAN).to_string(), "-sNaN(0x100)"); | |
| assert_eq!(Hex(f16::from_bits(u16::MAX)).to_string(), "-qNaN(0x1ff)"); | |
| assert_eq!(Hex(f16::INFINITY).to_string(), "+inf"); | |
| assert_eq!(Hex(f16::NEG_INFINITY).to_string(), "-inf"); | |
| } | |
| #[cfg(f128_enabled)] | |
| { | |
| assert_eq!( | |
| Hex(f128::MAX).to_string(), | |
| "+0x1.ffffffffffffffffffffffffffffp+16383" | |
| ); | |
| assert_eq!( | |
| Hex(f128::MIN).to_string(), | |
| "-0x1.ffffffffffffffffffffffffffffp+16383" | |
| ); | |
| assert_eq!(Hex(f128::ZERO).to_string(), "+0x0p+0"); | |
| assert_eq!(Hex(f128::NEG_ZERO).to_string(), "-0x0p+0"); | |
| assert_eq!( | |
| Hex(f128::NAN).to_string(), | |
| "+qNaN(0x0000000000000000000000000000)" | |
| ); | |
| assert_eq!( | |
| Hex(f128::SNAN).to_string(), | |
| "+sNaN(0x4000000000000000000000000000)" | |
| ); | |
| assert_eq!( | |
| Hex(f128::NEG_NAN).to_string(), | |
| "-qNaN(0x0000000000000000000000000000)" | |
| ); | |
| assert_eq!( | |
| Hex(f128::NEG_SNAN).to_string(), | |
| "-sNaN(0x4000000000000000000000000000)" | |
| ); | |
| assert_eq!( | |
| Hex(f128::from_bits(u128::MAX)).to_string(), | |
| "-qNaN(0x7fffffffffffffffffffffffffff)" | |
| ); | |
| assert_eq!(Hex(f128::INFINITY).to_string(), "+inf"); | |
| assert_eq!(Hex(f128::NEG_INFINITY).to_string(), "-inf"); | |
| } | |
| } | |
| } |