Add hf16! and hf128!

Expand the existing hex float functions and macros with versions that
work with `f16` and `f128`.
This commit is contained in:
Trevor Gross 2025-01-22 22:41:49 +00:00 committed by Trevor Gross
parent 42e22132b4
commit 8dc4ef6f0f
3 changed files with 266 additions and 6 deletions

View file

@ -4,6 +4,12 @@
use super::{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 {
f16::from_bits(parse_any(s, 16, 10) as u16)
}
/// Construct a 32-bit float from hex float representation (C-style)
pub const fn hf32(s: &str) -> f32 {
f32_from_bits(parse_any(s, 32, 23) as u32)
@ -14,6 +20,12 @@ pub const fn hf64(s: &str) -> f64 {
f64_from_bits(parse_any(s, 64, 52) as u64)
}
/// Construct a 128-bit float from hex float representation (C-style)
#[cfg(f128_enabled)]
pub const fn hf128(s: &str) -> f128 {
f128::from_bits(parse_any(s, 128, 112))
}
const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
let exp_bits: u32 = bits - sig_bits - 1;
let max_msb: i32 = (1 << (exp_bits - 1)) - 1;
@ -230,6 +242,57 @@ mod tests {
}
}
// 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),
];
for (s, exp) in checks {
println!("parsing {s}");
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 = [
@ -308,16 +371,67 @@ mod tests {
}
}
#[test]
fn test_f32_almost_extra_precision() {
// Exact maximum precision allowed
hf32("0x1.abcdeep+0");
// 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),
];
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() {
assert_eq!(hf32!("0x1.ffep+8").to_bits(), 0x43fff000u32);
assert_eq!(hf64!("0x1.ffep+8").to_bits(), 0x407ffe0000000000u64);
// FIXME(msrv): enable once parsing works
// #[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);
// FIXME(msrv): enable once parsing works
// #[cfg(f128_enabled)]
// assert_eq!(hf128!("0x1.ffep+8").to_bits(), 0x4007ffe0000000000000000000000000_u128);
}
}
@ -328,6 +442,69 @@ mod tests_panicking {
extern crate std;
use super::*;
// 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_almost_extra_precision() {
// Exact maximum precision allowed
hf16("0x1.ffcp+0");
}
#[test]
#[should_panic(expected = "the value is too precise")]
fn test_f16_extra_precision() {
// One bit more than the above.
hf16("0x1.ffdp+0");
}
#[test]
#[should_panic(expected = "the value is too huge")]
fn test_f16_overflow() {
// One bit more than the above.
hf16("0x1p+16");
}
#[test]
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]
#[should_panic(expected = "the value is too tiny")]
fn test_f16_too_tiny() {
hf16("0x1.p-25");
}
#[test]
#[should_panic(expected = "the value is too tiny")]
fn test_f16_also_too_tiny() {
hf16("0x0.8p-24");
}
#[test]
#[should_panic(expected = "the value is too tiny")]
fn test_f16_again_too_tiny() {
hf16("0x0.001p-13");
}
};
}
#[cfg(f16_enabled)]
f16_tests!();
#[test]
fn test_f32_almost_extra_precision() {
// Exact maximum precision allowed
hf32("0x1.abcdeep+0");
}
#[test]
#[should_panic]
fn test_f32_extra_precision2() {
@ -388,4 +565,61 @@ mod tests_panicking {
// One bit more than the above.
hf64("0x1.abcdabcdabcdf8p+0");
}
// 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_almost_extra_precision() {
// Exact maximum precision allowed
hf128("0x1.ffffffffffffffffffffffffffffp+16383");
}
#[test]
#[should_panic(expected = "the value is too precise")]
fn test_f128_extra_precision() {
// One bit more than the above.
hf128("0x1.ffffffffffffffffffffffffffff8p+16383");
}
#[test]
#[should_panic(expected = "the value is too huge")]
fn test_f128_overflow() {
// One bit more than the above.
hf128("0x1p+16384");
}
#[test]
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]
#[should_panic(expected = "the value is too tiny")]
fn test_f128_too_tiny() {
hf128("0x1.p-16495");
}
#[test]
#[should_panic(expected = "the value is too tiny")]
fn test_f128_again_too_tiny() {
hf128("0x0.0000000000000001p-16431");
}
#[test]
#[should_panic(expected = "the value is too tiny")]
fn test_f128_also_too_tiny() {
hf128("0x0.8p-16494");
}
};
}
#[cfg(f128_enabled)]
f128_tests!();
}

View file

@ -87,6 +87,17 @@ macro_rules! select_implementation {
(@cfg $provided:meta; $ex:expr) => { #[cfg($provided)] $ex };
}
/// Construct a 16-bit float from hex float representation (C-style), guaranteed to
/// evaluate at compile time.
#[cfg(f16_enabled)]
#[allow(unused_macros)]
macro_rules! hf16 {
($s:literal) => {{
const X: f16 = $crate::math::support::hf16($s);
X
}};
}
/// Construct a 32-bit float from hex float representation (C-style), guaranteed to
/// evaluate at compile time.
#[allow(unused_macros)]
@ -107,6 +118,17 @@ macro_rules! hf64 {
}};
}
/// Construct a 128-bit float from hex float representation (C-style), guaranteed to
/// evaluate at compile time.
#[cfg(f128_enabled)]
#[allow(unused_macros)]
macro_rules! hf128 {
($s:literal) => {{
const X: f128 = $crate::math::support::hf128($s);
X
}};
}
/// Assert `F::biteq` with better messages.
#[cfg(test)]
macro_rules! assert_biteq {

View file

@ -8,6 +8,10 @@ mod int_traits;
#[allow(unused_imports)]
pub use float_traits::{Float, IntTy};
pub(crate) use float_traits::{f32_from_bits, f64_from_bits};
#[cfg(f16_enabled)]
pub use hex_float::hf16;
#[cfg(f128_enabled)]
pub use hex_float::hf128;
#[allow(unused_imports)]
pub use hex_float::{hf32, hf64};
pub use int_traits::{CastFrom, CastInto, DInt, HInt, Int, MinInt};