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

Commit b00ec04

Browse files
committed
Introduce a wrapper type for IEEE hex float formatting
1 parent 2f770cd commit b00ec04

File tree

2 files changed

+181
-4
lines changed

2 files changed

+181
-4
lines changed

src/math/support/hex_float.rs

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
#![allow(dead_code)] // FIXME: remove once this gets used
44

5-
use super::{f32_from_bits, f64_from_bits};
5+
use core::fmt;
6+
7+
use super::{Float, f32_from_bits, f64_from_bits};
68

79
/// Construct a 16-bit float from hex float representation (C-style)
810
#[cfg(f16_enabled)]
@@ -42,7 +44,7 @@ pub const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
4244
Parsed::Finite { neg, sig, exp } => (neg, sig, exp),
4345
Parsed::Infinite { neg } => return ((neg as u128) << (bits - 1)) | exp_mask,
4446
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));
4648
}
4749
};
4850

@@ -206,8 +208,107 @@ const fn u128_ilog2(v: u128) -> u32 {
206208
u128::BITS - 1 - v.leading_zeros()
207209
}
208210

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+
209310
#[cfg(test)]
210-
mod tests {
311+
mod parse_tests {
211312
extern crate std;
212313
use std::{format, println};
213314

@@ -666,3 +767,79 @@ mod tests_panicking {
666767
#[cfg(f128_enabled)]
667768
f128_tests!();
668769
}
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+
}

src/math/support/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use hex_float::hf16;
1313
#[cfg(f128_enabled)]
1414
pub use hex_float::hf128;
1515
#[allow(unused_imports)]
16-
pub use hex_float::{hf32, hf64};
16+
pub use hex_float::{Hexf, hf32, hf64};
1717
pub use int_traits::{CastFrom, CastInto, DInt, HInt, Int, MinInt};
1818

1919
/// Hint to the compiler that the current path is cold.

0 commit comments

Comments
 (0)