|
1 | 1 | //! Helpful numeric operations.
|
2 | 2 |
|
3 | 3 | use std::cmp::min;
|
| 4 | +use std::fmt; |
4 | 5 | use std::ops::RangeInclusive;
|
5 | 6 |
|
6 | 7 | use libm::support::Float;
|
@@ -267,6 +268,106 @@ pub fn linear_ints(
|
267 | 268 | )
|
268 | 269 | }
|
269 | 270 |
|
| 271 | +/// Format a floating point number as its IEEE hex (`%a`) representation. |
| 272 | +pub struct Hexf<F>(pub F); |
| 273 | + |
| 274 | +// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs |
| 275 | +fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 276 | + if x.is_sign_negative() { |
| 277 | + write!(f, "-")?; |
| 278 | + } |
| 279 | + |
| 280 | + if x.is_nan() { |
| 281 | + return write!(f, "NaN"); |
| 282 | + } else if x.is_infinite() { |
| 283 | + return write!(f, "inf"); |
| 284 | + } else if *x == F::ZERO { |
| 285 | + return write!(f, "0x0p+0"); |
| 286 | + } |
| 287 | + |
| 288 | + let mut exponent = x.exp_unbiased(); |
| 289 | + let sig = x.to_bits() & F::SIG_MASK; |
| 290 | + |
| 291 | + let bias = F::EXP_BIAS as i32; |
| 292 | + // The mantissa MSB needs to be shifted up to the nearest nibble. |
| 293 | + let mshift = (4 - (F::SIG_BITS % 4)) % 4; |
| 294 | + let sig = sig << mshift; |
| 295 | + // The width is rounded up to the nearest char (4 bits) |
| 296 | + let mwidth = (F::SIG_BITS as usize + 3) / 4; |
| 297 | + let leading = if exponent == -bias { |
| 298 | + // subnormal number means we shift our output by 1 bit. |
| 299 | + exponent += 1; |
| 300 | + "0." |
| 301 | + } else { |
| 302 | + "1." |
| 303 | + }; |
| 304 | + let exp_sign = if exponent >= 0 { "+" } else { "" }; |
| 305 | + |
| 306 | + write!(f, "0x{leading}{sig:0mwidth$x}p{exp_sign}{exponent}") |
| 307 | +} |
| 308 | + |
| 309 | +#[cfg(f16_enabled)] |
| 310 | +impl fmt::LowerHex for Hexf<f16> { |
| 311 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 312 | + fmt_any_hex(&self.0, f) |
| 313 | + } |
| 314 | +} |
| 315 | + |
| 316 | +impl fmt::LowerHex for Hexf<f32> { |
| 317 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 318 | + fmt_any_hex(&self.0, f) |
| 319 | + } |
| 320 | +} |
| 321 | + |
| 322 | +impl fmt::LowerHex for Hexf<f64> { |
| 323 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 324 | + fmt_any_hex(&self.0, f) |
| 325 | + } |
| 326 | +} |
| 327 | + |
| 328 | +#[cfg(f128_enabled)] |
| 329 | +impl fmt::LowerHex for Hexf<f128> { |
| 330 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 331 | + fmt_any_hex(&self.0, f) |
| 332 | + } |
| 333 | +} |
| 334 | + |
| 335 | +impl fmt::LowerHex for Hexf<i32> { |
| 336 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 337 | + fmt::LowerHex::fmt(&self.0, f) |
| 338 | + } |
| 339 | +} |
| 340 | + |
| 341 | +impl<T1, T2> fmt::LowerHex for Hexf<(T1, T2)> |
| 342 | +where |
| 343 | + T1: Copy, |
| 344 | + T2: Copy, |
| 345 | + Hexf<T1>: fmt::LowerHex, |
| 346 | + Hexf<T2>: fmt::LowerHex, |
| 347 | +{ |
| 348 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 349 | + write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1)) |
| 350 | + } |
| 351 | +} |
| 352 | + |
| 353 | +impl<T> fmt::Debug for Hexf<T> |
| 354 | +where |
| 355 | + Hexf<T>: fmt::LowerHex, |
| 356 | +{ |
| 357 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 358 | + fmt::LowerHex::fmt(self, f) |
| 359 | + } |
| 360 | +} |
| 361 | + |
| 362 | +impl<T> fmt::Display for Hexf<T> |
| 363 | +where |
| 364 | + Hexf<T>: fmt::LowerHex, |
| 365 | +{ |
| 366 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 367 | + fmt::LowerHex::fmt(self, f) |
| 368 | + } |
| 369 | +} |
| 370 | + |
270 | 371 | #[cfg(test)]
|
271 | 372 | mod tests {
|
272 | 373 | use std::cmp::max;
|
@@ -527,3 +628,77 @@ mod tests {
|
527 | 628 | assert_eq!(neg_max_snan.to_bits(), 0b1_1111_011);
|
528 | 629 | }
|
529 | 630 | }
|
| 631 | + |
| 632 | +#[cfg(test)] |
| 633 | +mod tests_hex { |
| 634 | + use super::*; |
| 635 | + |
| 636 | + #[test] |
| 637 | + #[cfg(f16_enabled)] |
| 638 | + fn test_f16() { |
| 639 | + use libm::support::hf16; |
| 640 | + |
| 641 | + // Exhaustively check that `f16` roundtrips. |
| 642 | + for x in 0..=u16::MAX { |
| 643 | + let f = f16::from_bits(x); |
| 644 | + let s = format!("{}", Hexf(f)); |
| 645 | + let from_s = hf16(&s); |
| 646 | + |
| 647 | + if f.is_nan() && from_s.is_nan() { |
| 648 | + continue; |
| 649 | + } |
| 650 | + |
| 651 | + assert_eq!( |
| 652 | + f.to_bits(), |
| 653 | + from_s.to_bits(), |
| 654 | + "{f:?} formatted as {s} but parsed as {from_s:?}" |
| 655 | + ); |
| 656 | + } |
| 657 | + } |
| 658 | + |
| 659 | + #[test] |
| 660 | + fn spot_checks() { |
| 661 | + assert_eq!(Hexf(f32::MAX).to_string(), "0x1.fffffep+127"); |
| 662 | + assert_eq!(Hexf(f64::MAX).to_string(), "0x1.fffffffffffffp+1023"); |
| 663 | + |
| 664 | + assert_eq!(Hexf(f32::MIN).to_string(), "-0x1.fffffep+127"); |
| 665 | + assert_eq!(Hexf(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023"); |
| 666 | + |
| 667 | + assert_eq!(Hexf(f32::ZERO).to_string(), "0x0p+0"); |
| 668 | + assert_eq!(Hexf(f64::ZERO).to_string(), "0x0p+0"); |
| 669 | + |
| 670 | + assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0"); |
| 671 | + assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0"); |
| 672 | + |
| 673 | + assert_eq!(Hexf(f32::NAN).to_string(), "NaN"); |
| 674 | + assert_eq!(Hexf(f64::NAN).to_string(), "NaN"); |
| 675 | + |
| 676 | + assert_eq!(Hexf(f32::INFINITY).to_string(), "inf"); |
| 677 | + assert_eq!(Hexf(f64::INFINITY).to_string(), "inf"); |
| 678 | + |
| 679 | + assert_eq!(Hexf(f32::NEG_INFINITY).to_string(), "-inf"); |
| 680 | + assert_eq!(Hexf(f64::NEG_INFINITY).to_string(), "-inf"); |
| 681 | + |
| 682 | + #[cfg(f16_enabled)] |
| 683 | + { |
| 684 | + assert_eq!(Hexf(f16::MAX).to_string(), "0x1.ffcp+15"); |
| 685 | + assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15"); |
| 686 | + assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0"); |
| 687 | + assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0"); |
| 688 | + assert_eq!(Hexf(f16::NAN).to_string(), "NaN"); |
| 689 | + assert_eq!(Hexf(f16::INFINITY).to_string(), "inf"); |
| 690 | + assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf"); |
| 691 | + } |
| 692 | + |
| 693 | + #[cfg(f128_enabled)] |
| 694 | + { |
| 695 | + assert_eq!(Hexf(f128::MAX).to_string(), "0x1.ffffffffffffffffffffffffffffp+16383"); |
| 696 | + assert_eq!(Hexf(f128::MIN).to_string(), "-0x1.ffffffffffffffffffffffffffffp+16383"); |
| 697 | + assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0"); |
| 698 | + assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0"); |
| 699 | + assert_eq!(Hexf(f128::NAN).to_string(), "NaN"); |
| 700 | + assert_eq!(Hexf(f128::INFINITY).to_string(), "inf"); |
| 701 | + assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf"); |
| 702 | + } |
| 703 | + } |
| 704 | +} |
0 commit comments