|
2 | 2 |
|
3 | 3 | #![allow(dead_code)] // FIXME: remove once this gets used
|
4 | 4 |
|
5 |
| -use super::{f32_from_bits, f64_from_bits}; |
| 5 | +use core::fmt; |
| 6 | + |
| 7 | +use super::{Float, f32_from_bits, f64_from_bits}; |
6 | 8 |
|
7 | 9 | /// Construct a 16-bit float from hex float representation (C-style)
|
8 | 10 | #[cfg(f16_enabled)]
|
@@ -42,7 +44,7 @@ pub const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
|
42 | 44 | Parsed::Finite { neg, sig, exp } => (neg, sig, exp),
|
43 | 45 | Parsed::Infinite { neg } => return ((neg as u128) << (bits - 1)) | exp_mask,
|
44 | 46 | Parsed::Nan { neg } => {
|
45 |
| - return ((neg as u128) << (bits - 1)) | exp_mask | 1 << (sig_bits - 1); |
| 47 | + return ((neg as u128) << (bits - 1)) | exp_mask | (1 << (sig_bits - 1)); |
46 | 48 | }
|
47 | 49 | };
|
48 | 50 |
|
@@ -206,8 +208,107 @@ const fn u128_ilog2(v: u128) -> u32 {
|
206 | 208 | u128::BITS - 1 - v.leading_zeros()
|
207 | 209 | }
|
208 | 210 |
|
| 211 | +/// Format a floating point number as its IEEE hex (`%a`) representation. |
| 212 | +pub struct Hexf<F>(pub F); |
| 213 | + |
| 214 | +// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs |
| 215 | +fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 216 | + if x.is_sign_negative() { |
| 217 | + write!(f, "-")?; |
| 218 | + } |
| 219 | + |
| 220 | + if x.is_nan() { |
| 221 | + return write!(f, "NaN"); |
| 222 | + } else if x.is_infinite() { |
| 223 | + return write!(f, "inf"); |
| 224 | + } else if *x == F::ZERO { |
| 225 | + return write!(f, "0x0p+0"); |
| 226 | + } |
| 227 | + |
| 228 | + let mut exponent = x.exp_unbiased(); |
| 229 | + let sig = x.to_bits() & F::SIG_MASK; |
| 230 | + |
| 231 | + let bias = F::EXP_BIAS as i32; |
| 232 | + // The mantissa MSB needs to be shifted up to the nearest nibble. |
| 233 | + let mshift = (4 - (F::SIG_BITS % 4)) % 4; |
| 234 | + let sig = sig << mshift; |
| 235 | + // The width is rounded up to the nearest char (4 bits) |
| 236 | + let mwidth = (F::SIG_BITS as usize + 3) / 4; |
| 237 | + let leading = if exponent == -bias { |
| 238 | + // subnormal number means we shift our output by 1 bit. |
| 239 | + exponent += 1; |
| 240 | + "0." |
| 241 | + } else { |
| 242 | + "1." |
| 243 | + }; |
| 244 | + |
| 245 | + write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}") |
| 246 | +} |
| 247 | + |
| 248 | +#[cfg(f16_enabled)] |
| 249 | +impl fmt::LowerHex for Hexf<f16> { |
| 250 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 251 | + fmt_any_hex(&self.0, f) |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +impl fmt::LowerHex for Hexf<f32> { |
| 256 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 257 | + fmt_any_hex(&self.0, f) |
| 258 | + } |
| 259 | +} |
| 260 | + |
| 261 | +impl fmt::LowerHex for Hexf<f64> { |
| 262 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 263 | + fmt_any_hex(&self.0, f) |
| 264 | + } |
| 265 | +} |
| 266 | + |
| 267 | +#[cfg(f128_enabled)] |
| 268 | +impl fmt::LowerHex for Hexf<f128> { |
| 269 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 270 | + fmt_any_hex(&self.0, f) |
| 271 | + } |
| 272 | +} |
| 273 | + |
| 274 | +impl fmt::LowerHex for Hexf<i32> { |
| 275 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 276 | + fmt::LowerHex::fmt(&self.0, f) |
| 277 | + } |
| 278 | +} |
| 279 | + |
| 280 | +impl<T1, T2> fmt::LowerHex for Hexf<(T1, T2)> |
| 281 | +where |
| 282 | + T1: Copy, |
| 283 | + T2: Copy, |
| 284 | + Hexf<T1>: fmt::LowerHex, |
| 285 | + Hexf<T2>: fmt::LowerHex, |
| 286 | +{ |
| 287 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 288 | + write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1)) |
| 289 | + } |
| 290 | +} |
| 291 | + |
| 292 | +impl<T> fmt::Debug for Hexf<T> |
| 293 | +where |
| 294 | + Hexf<T>: fmt::LowerHex, |
| 295 | +{ |
| 296 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 297 | + fmt::LowerHex::fmt(self, f) |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +impl<T> fmt::Display for Hexf<T> |
| 302 | +where |
| 303 | + Hexf<T>: fmt::LowerHex, |
| 304 | +{ |
| 305 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 306 | + fmt::LowerHex::fmt(self, f) |
| 307 | + } |
| 308 | +} |
| 309 | + |
209 | 310 | #[cfg(test)]
|
210 |
| -mod tests { |
| 311 | +mod parse_tests { |
211 | 312 | extern crate std;
|
212 | 313 | use std::{format, println};
|
213 | 314 |
|
@@ -666,3 +767,79 @@ mod tests_panicking {
|
666 | 767 | #[cfg(f128_enabled)]
|
667 | 768 | f128_tests!();
|
668 | 769 | }
|
| 770 | + |
| 771 | +#[cfg(test)] |
| 772 | +mod print_tests { |
| 773 | + extern crate std; |
| 774 | + use std::format; |
| 775 | + use std::string::ToString; |
| 776 | + |
| 777 | + use super::*; |
| 778 | + |
| 779 | + #[test] |
| 780 | + #[cfg(f16_enabled)] |
| 781 | + fn test_f16() { |
| 782 | + // Exhaustively check that `f16` roundtrips. |
| 783 | + for x in 0..=u16::MAX { |
| 784 | + let f = f16::from_bits(x); |
| 785 | + let s = format!("{}", Hexf(f)); |
| 786 | + let from_s = hf16(&s); |
| 787 | + |
| 788 | + if f.is_nan() && from_s.is_nan() { |
| 789 | + continue; |
| 790 | + } |
| 791 | + |
| 792 | + assert_eq!( |
| 793 | + f.to_bits(), |
| 794 | + from_s.to_bits(), |
| 795 | + "{f:?} formatted as {s} but parsed as {from_s:?}" |
| 796 | + ); |
| 797 | + } |
| 798 | + } |
| 799 | + |
| 800 | + #[test] |
| 801 | + fn spot_checks() { |
| 802 | + assert_eq!(Hexf(f32::MAX).to_string(), "0x1.fffffep+127"); |
| 803 | + assert_eq!(Hexf(f64::MAX).to_string(), "0x1.fffffffffffffp+1023"); |
| 804 | + |
| 805 | + assert_eq!(Hexf(f32::MIN).to_string(), "-0x1.fffffep+127"); |
| 806 | + assert_eq!(Hexf(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023"); |
| 807 | + |
| 808 | + assert_eq!(Hexf(f32::ZERO).to_string(), "0x0p+0"); |
| 809 | + assert_eq!(Hexf(f64::ZERO).to_string(), "0x0p+0"); |
| 810 | + |
| 811 | + assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0"); |
| 812 | + assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0"); |
| 813 | + |
| 814 | + assert_eq!(Hexf(f32::NAN).to_string(), "NaN"); |
| 815 | + assert_eq!(Hexf(f64::NAN).to_string(), "NaN"); |
| 816 | + |
| 817 | + assert_eq!(Hexf(f32::INFINITY).to_string(), "inf"); |
| 818 | + assert_eq!(Hexf(f64::INFINITY).to_string(), "inf"); |
| 819 | + |
| 820 | + assert_eq!(Hexf(f32::NEG_INFINITY).to_string(), "-inf"); |
| 821 | + assert_eq!(Hexf(f64::NEG_INFINITY).to_string(), "-inf"); |
| 822 | + |
| 823 | + #[cfg(f16_enabled)] |
| 824 | + { |
| 825 | + assert_eq!(Hexf(f16::MAX).to_string(), "0x1.ffcp+15"); |
| 826 | + assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15"); |
| 827 | + assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0"); |
| 828 | + assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0"); |
| 829 | + assert_eq!(Hexf(f16::NAN).to_string(), "NaN"); |
| 830 | + assert_eq!(Hexf(f16::INFINITY).to_string(), "inf"); |
| 831 | + assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf"); |
| 832 | + } |
| 833 | + |
| 834 | + #[cfg(f128_enabled)] |
| 835 | + { |
| 836 | + assert_eq!(Hexf(f128::MAX).to_string(), "0x1.ffffffffffffffffffffffffffffp+16383"); |
| 837 | + assert_eq!(Hexf(f128::MIN).to_string(), "-0x1.ffffffffffffffffffffffffffffp+16383"); |
| 838 | + assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0"); |
| 839 | + assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0"); |
| 840 | + assert_eq!(Hexf(f128::NAN).to_string(), "NaN"); |
| 841 | + assert_eq!(Hexf(f128::INFINITY).to_string(), "inf"); |
| 842 | + assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf"); |
| 843 | + } |
| 844 | + } |
| 845 | +} |
0 commit comments