Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 09428e4

Browse files
committed
Introduce a wrapper type for IEEE hex float formatting
1 parent 18198a2 commit 09428e4

File tree

2 files changed

+176
-1
lines changed

2 files changed

+176
-1
lines changed

crates/libm-test/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use std::time::SystemTime;
2222

2323
pub use f8_impl::{f8, hf8};
2424
pub use libm::support::{Float, Int, IntTy, MinInt};
25-
pub use num::{FloatExt, linear_ints, logspace};
25+
pub use num::{FloatExt, Hexf, linear_ints, logspace};
2626
pub use op::{
2727
BaseName, FloatTy, Identifier, MathOp, OpCFn, OpCRet, OpFTy, OpRustArgs, OpRustFn, OpRustRet,
2828
Ty,

crates/libm-test/src/num.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Helpful numeric operations.
22
33
use std::cmp::min;
4+
use std::fmt;
45
use std::ops::RangeInclusive;
56

67
use libm::support::Float;
@@ -267,6 +268,106 @@ pub fn linear_ints(
267268
)
268269
}
269270

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+
270371
#[cfg(test)]
271372
mod tests {
272373
use std::cmp::max;
@@ -527,3 +628,77 @@ mod tests {
527628
assert_eq!(neg_max_snan.to_bits(), 0b1_1111_011);
528629
}
529630
}
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

Comments
 (0)