float: Add tests for f16 conversions to and from decimal

Extend the existing tests for `f32` and `f64` with versions that include
`f16`'s new printing and parsing implementations.

Co-authored-by: Speedy_Lex <alex.ciocildau@gmail.com>
This commit is contained in:
Trevor Gross
2025-03-08 03:39:04 +00:00
parent 6fc60b8b04
commit 977d841869
9 changed files with 498 additions and 74 deletions
@@ -7,6 +7,20 @@
const FPATHS_F64: &[FPath<f64>] =
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(target_has_reliable_f16)]
fn check_fast_path_f16() {
const FPATHS_F16: &[FPath<f16>] =
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F16.iter().copied() {
let dec = Decimal { exponent, mantissa, negative, many_digits };
let actual = dec.try_fast_path::<f16>();
assert_eq!(actual, expected);
}
}
#[test]
fn check_fast_path_f32() {
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F32.iter().copied() {
@@ -1,5 +1,24 @@
use core::num::dec2flt::float::RawFloat;
// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(target_has_reliable_f16)]
fn test_f16_integer_decode() {
assert_eq!(3.14159265359f16.integer_decode(), (1608, -9, 1));
assert_eq!((-8573.5918555f16).integer_decode(), (1072, 3, -1));
#[cfg(not(miri))] // miri doesn't have powf16
assert_eq!(2f16.powf(14.0).integer_decode(), (1 << 10, 4, 1));
assert_eq!(0f16.integer_decode(), (0, -25, 1));
assert_eq!((-0f16).integer_decode(), (0, -25, -1));
assert_eq!(f16::INFINITY.integer_decode(), (1 << 10, 6, 1));
assert_eq!(f16::NEG_INFINITY.integer_decode(), (1 << 10, 6, -1));
// Ignore the "sign" (quiet / signalling flag) of NAN.
// It can vary between runtime operations and LLVM folding.
let (nan_m, nan_p, _nan_s) = f16::NAN.integer_decode();
assert_eq!((nan_m, nan_p), (1536, 6));
}
#[test]
fn test_f32_integer_decode() {
assert_eq!(3.14159265359f32.integer_decode(), (13176795, -22, 1));
@@ -34,6 +53,27 @@ fn test_f64_integer_decode() {
/* Sanity checks of computed magic numbers */
// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(target_has_reliable_f16)]
fn test_f16_consts() {
assert_eq!(<f16 as RawFloat>::INFINITY, f16::INFINITY);
assert_eq!(<f16 as RawFloat>::NEG_INFINITY, -f16::INFINITY);
assert_eq!(<f16 as RawFloat>::NAN.to_bits(), f16::NAN.to_bits());
assert_eq!(<f16 as RawFloat>::NEG_NAN.to_bits(), (-f16::NAN).to_bits());
assert_eq!(<f16 as RawFloat>::SIG_BITS, 10);
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -22);
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 5);
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_FAST_PATH, -4);
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_FAST_PATH, 4);
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 7);
assert_eq!(<f16 as RawFloat>::EXP_MIN, -14);
assert_eq!(<f16 as RawFloat>::EXP_SAT, 0x1f);
assert_eq!(<f16 as RawFloat>::SMALLEST_POWER_OF_TEN, -27);
assert_eq!(<f16 as RawFloat>::LARGEST_POWER_OF_TEN, 4);
assert_eq!(<f16 as RawFloat>::MAX_MANTISSA_FAST_PATH, 2048);
}
#[test]
fn test_f32_consts() {
assert_eq!(<f32 as RawFloat>::INFINITY, f32::INFINITY);
+102 -31
View File
@@ -1,6 +1,12 @@
use core::num::dec2flt::float::RawFloat;
use core::num::dec2flt::lemire::compute_float;
#[cfg(target_has_reliable_f16)]
fn compute_float16(q: i64, w: u64) -> (i32, u64) {
let fp = compute_float::<f16>(q, w);
(fp.p_biased, fp.m)
}
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
let fp = compute_float::<f32>(q, w);
(fp.p_biased, fp.m)
@@ -11,23 +17,73 @@ fn compute_float64(q: i64, w: u64) -> (i32, u64) {
(fp.p_biased, fp.m)
}
// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(target_has_reliable_f16)]
fn compute_float_f16_rounding() {
// The maximum integer that cna be converted to a `f16` without lost precision.
let val = 1 << 11;
let scale = 10_u64.pow(10);
// These test near-halfway cases for half-precision floats.
assert_eq!(compute_float16(0, val), (26, 0));
assert_eq!(compute_float16(0, val + 1), (26, 0));
assert_eq!(compute_float16(0, val + 2), (26, 1));
assert_eq!(compute_float16(0, val + 3), (26, 2));
assert_eq!(compute_float16(0, val + 4), (26, 2));
// For the next power up, the two nearest representable numbers are twice as far apart.
let val2 = 1 << 12;
assert_eq!(compute_float16(0, val2), (27, 0));
assert_eq!(compute_float16(0, val2 + 2), (27, 0));
assert_eq!(compute_float16(0, val2 + 4), (27, 1));
assert_eq!(compute_float16(0, val2 + 6), (27, 2));
assert_eq!(compute_float16(0, val2 + 8), (27, 2));
// These are examples of the above tests, with digits from the exponent shifted
// to the mantissa.
assert_eq!(compute_float16(-10, val * scale), (26, 0));
assert_eq!(compute_float16(-10, (val + 1) * scale), (26, 0));
assert_eq!(compute_float16(-10, (val + 2) * scale), (26, 1));
// Let's check the lines to see if anything is different in table...
assert_eq!(compute_float16(-10, (val + 3) * scale), (26, 2));
assert_eq!(compute_float16(-10, (val + 4) * scale), (26, 2));
// Check the rounding point between infinity and the next representable number down
assert_eq!(compute_float16(4, 6), (f16::INFINITE_POWER - 1, 851));
assert_eq!(compute_float16(4, 7), (f16::INFINITE_POWER, 0)); // infinity
assert_eq!(compute_float16(2, 655), (f16::INFINITE_POWER - 1, 1023));
}
#[test]
fn compute_float_f32_rounding() {
// These test near-halfway cases for single-precision floats.
assert_eq!(compute_float32(0, 16777216), (151, 0));
assert_eq!(compute_float32(0, 16777217), (151, 0));
assert_eq!(compute_float32(0, 16777218), (151, 1));
assert_eq!(compute_float32(0, 16777219), (151, 2));
assert_eq!(compute_float32(0, 16777220), (151, 2));
// the maximum integer that cna be converted to a `f32` without lost precision.
let val = 1 << 24;
let scale = 10_u64.pow(10);
// These are examples of the above tests, with
// digits from the exponent shifted to the mantissa.
assert_eq!(compute_float32(-10, 167772160000000000), (151, 0));
assert_eq!(compute_float32(-10, 167772170000000000), (151, 0));
assert_eq!(compute_float32(-10, 167772180000000000), (151, 1));
// These test near-halfway cases for single-precision floats.
assert_eq!(compute_float32(0, val), (151, 0));
assert_eq!(compute_float32(0, val + 1), (151, 0));
assert_eq!(compute_float32(0, val + 2), (151, 1));
assert_eq!(compute_float32(0, val + 3), (151, 2));
assert_eq!(compute_float32(0, val + 4), (151, 2));
// For the next power up, the two nearest representable numbers are twice as far apart.
let val2 = 1 << 25;
assert_eq!(compute_float32(0, val2), (152, 0));
assert_eq!(compute_float32(0, val2 + 2), (152, 0));
assert_eq!(compute_float32(0, val2 + 4), (152, 1));
assert_eq!(compute_float32(0, val2 + 6), (152, 2));
assert_eq!(compute_float32(0, val2 + 8), (152, 2));
// These are examples of the above tests, with digits from the exponent shifted
// to the mantissa.
assert_eq!(compute_float32(-10, val * scale), (151, 0));
assert_eq!(compute_float32(-10, (val + 1) * scale), (151, 0));
assert_eq!(compute_float32(-10, (val + 2) * scale), (151, 1));
// Let's check the lines to see if anything is different in table...
assert_eq!(compute_float32(-10, 167772190000000000), (151, 2));
assert_eq!(compute_float32(-10, 167772200000000000), (151, 2));
assert_eq!(compute_float32(-10, (val + 3) * scale), (151, 2));
assert_eq!(compute_float32(-10, (val + 4) * scale), (151, 2));
// Check the rounding point between infinity and the next representable number down
assert_eq!(compute_float32(38, 3), (f32::INFINITE_POWER - 1, 6402534));
@@ -37,23 +93,38 @@ fn compute_float_f32_rounding() {
#[test]
fn compute_float_f64_rounding() {
// These test near-halfway cases for double-precision floats.
assert_eq!(compute_float64(0, 9007199254740992), (1076, 0));
assert_eq!(compute_float64(0, 9007199254740993), (1076, 0));
assert_eq!(compute_float64(0, 9007199254740994), (1076, 1));
assert_eq!(compute_float64(0, 9007199254740995), (1076, 2));
assert_eq!(compute_float64(0, 9007199254740996), (1076, 2));
assert_eq!(compute_float64(0, 18014398509481984), (1077, 0));
assert_eq!(compute_float64(0, 18014398509481986), (1077, 0));
assert_eq!(compute_float64(0, 18014398509481988), (1077, 1));
assert_eq!(compute_float64(0, 18014398509481990), (1077, 2));
assert_eq!(compute_float64(0, 18014398509481992), (1077, 2));
// The maximum integer that cna be converted to a `f64` without lost precision.
let val = 1 << 53;
let scale = 1000;
// These are examples of the above tests, with
// digits from the exponent shifted to the mantissa.
assert_eq!(compute_float64(-3, 9007199254740992000), (1076, 0));
assert_eq!(compute_float64(-3, 9007199254740993000), (1076, 0));
assert_eq!(compute_float64(-3, 9007199254740994000), (1076, 1));
assert_eq!(compute_float64(-3, 9007199254740995000), (1076, 2));
assert_eq!(compute_float64(-3, 9007199254740996000), (1076, 2));
// These test near-halfway cases for double-precision floats.
assert_eq!(compute_float64(0, val), (1076, 0));
assert_eq!(compute_float64(0, val + 1), (1076, 0));
assert_eq!(compute_float64(0, val + 2), (1076, 1));
assert_eq!(compute_float64(0, val + 3), (1076, 2));
assert_eq!(compute_float64(0, val + 4), (1076, 2));
// For the next power up, the two nearest representable numbers are twice as far apart.
let val2 = 1 << 54;
assert_eq!(compute_float64(0, val2), (1077, 0));
assert_eq!(compute_float64(0, val2 + 2), (1077, 0));
assert_eq!(compute_float64(0, val2 + 4), (1077, 1));
assert_eq!(compute_float64(0, val2 + 6), (1077, 2));
assert_eq!(compute_float64(0, val2 + 8), (1077, 2));
// These are examples of the above tests, with digits from the exponent shifted
// to the mantissa.
assert_eq!(compute_float64(-3, val * scale), (1076, 0));
assert_eq!(compute_float64(-3, (val + 1) * scale), (1076, 0));
assert_eq!(compute_float64(-3, (val + 2) * scale), (1076, 1));
assert_eq!(compute_float64(-3, (val + 3) * scale), (1076, 2));
assert_eq!(compute_float64(-3, (val + 4) * scale), (1076, 2));
// Check the rounding point between infinity and the next representable number down
assert_eq!(compute_float64(308, 1), (f64::INFINITE_POWER - 1, 506821272651936));
assert_eq!(compute_float64(308, 2), (f64::INFINITE_POWER, 0)); // infinity
assert_eq!(
compute_float64(292, 17976931348623157),
(f64::INFINITE_POWER - 1, 4503599627370495)
);
}
+56 -9
View File
@@ -11,15 +11,23 @@
// Requires a *polymorphic literal*, i.e., one that can serve as f64 as well as f32.
macro_rules! test_literal {
($x: expr) => {{
#[cfg(target_has_reliable_f16)]
let x16: f16 = $x;
let x32: f32 = $x;
let x64: f64 = $x;
let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
for input in inputs {
assert_eq!(input.parse(), Ok(x64));
assert_eq!(input.parse(), Ok(x32));
assert_eq!(input.parse(), Ok(x64), "failed f64 {input}");
assert_eq!(input.parse(), Ok(x32), "failed f32 {input}");
#[cfg(target_has_reliable_f16)]
assert_eq!(input.parse(), Ok(x16), "failed f16 {input}");
let neg_input = format!("-{input}");
assert_eq!(neg_input.parse(), Ok(-x64));
assert_eq!(neg_input.parse(), Ok(-x32));
assert_eq!(neg_input.parse(), Ok(-x64), "failed f64 {neg_input}");
assert_eq!(neg_input.parse(), Ok(-x32), "failed f32 {neg_input}");
#[cfg(target_has_reliable_f16)]
assert_eq!(neg_input.parse(), Ok(-x16), "failed f16 {neg_input}");
}
}};
}
@@ -84,48 +92,87 @@ fn fast_path_correct() {
test_literal!(1.448997445238699);
}
// FIXME(f16_f128): remove gates once tests work on all targets
#[test]
fn lonely_dot() {
#[cfg(target_has_reliable_f16)]
assert!(".".parse::<f16>().is_err());
assert!(".".parse::<f32>().is_err());
assert!(".".parse::<f64>().is_err());
}
#[test]
fn exponentiated_dot() {
#[cfg(target_has_reliable_f16)]
assert!(".e0".parse::<f16>().is_err());
assert!(".e0".parse::<f32>().is_err());
assert!(".e0".parse::<f64>().is_err());
}
#[test]
fn lonely_sign() {
assert!("+".parse::<f32>().is_err());
assert!("-".parse::<f64>().is_err());
#[cfg(target_has_reliable_f16)]
assert!("+".parse::<f16>().is_err());
assert!("-".parse::<f32>().is_err());
assert!("+".parse::<f64>().is_err());
}
#[test]
fn whitespace() {
#[cfg(target_has_reliable_f16)]
assert!("1.0 ".parse::<f16>().is_err());
assert!(" 1.0".parse::<f32>().is_err());
assert!("1.0 ".parse::<f64>().is_err());
}
#[test]
fn nan() {
#[cfg(target_has_reliable_f16)]
{
assert!("NaN".parse::<f16>().unwrap().is_nan());
assert!("-NaN".parse::<f16>().unwrap().is_nan());
}
assert!("NaN".parse::<f32>().unwrap().is_nan());
assert!("-NaN".parse::<f32>().unwrap().is_nan());
assert!("NaN".parse::<f64>().unwrap().is_nan());
assert!("-NaN".parse::<f64>().unwrap().is_nan());
}
#[test]
fn inf() {
assert_eq!("inf".parse(), Ok(f64::INFINITY));
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
#[cfg(target_has_reliable_f16)]
{
assert_eq!("inf".parse(), Ok(f16::INFINITY));
assert_eq!("-inf".parse(), Ok(f16::NEG_INFINITY));
}
assert_eq!("inf".parse(), Ok(f32::INFINITY));
assert_eq!("-inf".parse(), Ok(f32::NEG_INFINITY));
assert_eq!("inf".parse(), Ok(f64::INFINITY));
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
}
#[test]
fn massive_exponent() {
#[cfg(target_has_reliable_f16)]
{
let max = i16::MAX;
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f16));
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
}
let max = i32::MAX;
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f32));
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
let max = i64::MAX;
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0));
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f64));
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
}
+21 -2
View File
@@ -10,6 +10,9 @@ fn new_dec(e: i64, m: u64) -> Decimal {
fn missing_pieces() {
let permutations = &[".e", "1e", "e4", "e", ".12e", "321.e", "32.12e+", "12.32e-"];
for &s in permutations {
#[cfg(target_has_reliable_f16)]
assert_eq!(dec2flt::<f16>(s), Err(pfe_invalid()));
assert_eq!(dec2flt::<f32>(s), Err(pfe_invalid()));
assert_eq!(dec2flt::<f64>(s), Err(pfe_invalid()));
}
}
@@ -17,15 +20,31 @@ fn missing_pieces() {
#[test]
fn invalid_chars() {
let invalid = "r,?<j";
let error = Err(pfe_invalid());
let valid_strings = &["123", "666.", ".1", "5e1", "7e-3", "0.0e+1"];
for c in invalid.chars() {
for s in valid_strings {
for i in 0..s.len() {
let mut input = String::new();
input.push_str(s);
input.insert(i, c);
assert!(dec2flt::<f64>(&input) == error, "did not reject invalid {:?}", input);
#[cfg(target_has_reliable_f16)]
assert_eq!(
dec2flt::<f16>(&input),
Err(pfe_invalid()),
"f16 did not reject invalid {input:?}",
);
assert_eq!(
dec2flt::<f32>(&input),
Err(pfe_invalid()),
"f32 did not reject invalid {input:?}",
);
assert_eq!(
dec2flt::<f64>(&input),
Err(pfe_invalid()),
"f64 did not reject invalid {input:?}",
);
}
}
}
+196 -32
View File
@@ -16,7 +16,7 @@ mod strategy {
pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
match decode(v).1 {
FullDecoded::Finite(decoded) => decoded,
full_decoded => panic!("expected finite, got {full_decoded:?} instead"),
full_decoded => panic!("expected finite, got {full_decoded:?} instead for {v:?}"),
}
}
@@ -75,6 +75,11 @@ macro_rules! try_fixed {
})
}
#[cfg(target_has_reliable_f16)]
fn ldexp_f16(a: f16, b: i32) -> f16 {
ldexp_f64(a as f64, b) as f16
}
fn ldexp_f32(a: f32, b: i32) -> f32 {
ldexp_f64(a as f64, b) as f32
}
@@ -176,6 +181,13 @@ trait TestableFloat: DecodableFloat + fmt::Display {
fn ldexpi(f: i64, exp: isize) -> Self;
}
#[cfg(target_has_reliable_f16)]
impl TestableFloat for f16 {
fn ldexpi(f: i64, exp: isize) -> Self {
f as Self * (exp as Self).exp2()
}
}
impl TestableFloat for f32 {
fn ldexpi(f: i64, exp: isize) -> Self {
f as Self * (exp as Self).exp2()
@@ -225,6 +237,76 @@ macro_rules! check_exact_one {
//
// [1] Vern Paxson, A Program for Testing IEEE Decimal-Binary Conversion
// ftp://ftp.ee.lbl.gov/testbase-report.ps.Z
// or https://www.icir.org/vern/papers/testbase-report.pdf
#[cfg(target_has_reliable_f16)]
pub fn f16_shortest_sanity_test<F>(mut f: F)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
// 0.0999145507813
// 0.0999755859375
// 0.100036621094
check_shortest!(f(0.1f16) => b"1", 0);
// 0.3330078125
// 0.333251953125 (1/3 in the default rounding)
// 0.33349609375
check_shortest!(f(1.0f16/3.0) => b"3333", 0);
// 10^1 * 0.3138671875
// 10^1 * 0.3140625
// 10^1 * 0.3142578125
check_shortest!(f(3.14f16) => b"314", 1);
// 10^18 * 0.31415916243714048
// 10^18 * 0.314159196796878848
// 10^18 * 0.314159231156617216
check_shortest!(f(3.1415e4f16) => b"3141", 5);
// regression test for decoders
// 10^2 * 0.31984375
// 10^2 * 0.32
// 10^2 * 0.3203125
check_shortest!(f(ldexp_f16(1.0, 5)) => b"32", 2);
// 10^5 * 0.65472
// 10^5 * 0.65504
// 10^5 * 0.65536
check_shortest!(f(f16::MAX) => b"655", 5);
// 10^-4 * 0.60975551605224609375
// 10^-4 * 0.6103515625
// 10^-4 * 0.61094760894775390625
check_shortest!(f(f16::MIN_POSITIVE) => b"6104", -4);
// 10^-9 * 0
// 10^-9 * 0.59604644775390625
// 10^-8 * 0.11920928955078125
let minf16 = ldexp_f16(1.0, -24);
check_shortest!(f(minf16) => b"6", -7);
}
#[cfg(target_has_reliable_f16)]
pub fn f16_exact_sanity_test<F>(mut f: F)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
{
let minf16 = ldexp_f16(1.0, -24);
check_exact!(f(0.1f16) => b"999755859375 ", -1);
check_exact!(f(0.5f16) => b"5 ", 0);
check_exact!(f(1.0f16/3.0) => b"333251953125 ", 0);
check_exact!(f(3.141f16) => b"3140625 ", 1);
check_exact!(f(3.141e4f16) => b"31408 ", 5);
check_exact!(f(f16::MAX) => b"65504 ", 5);
check_exact!(f(f16::MIN_POSITIVE) => b"6103515625 ", -4);
check_exact!(f(minf16) => b"59604644775390625", -7);
// FIXME(f16_f128): these should gain the check_exact_one tests like `f32` and `f64` have,
// but these values are not easy to generate. The algorithm from the Paxon paper [1] needs
// to be adapted to binary16.
}
pub fn f32_shortest_sanity_test<F>(mut f: F)
where
@@ -553,23 +635,45 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
assert_eq!(to_string(f, 1.9971e20, Minus, 1), "199710000000000000000.0");
assert_eq!(to_string(f, 1.9971e20, Minus, 8), "199710000000000000000.00000000");
assert_eq!(to_string(f, f32::MAX, Minus, 0), format!("34028235{:0>31}", ""));
assert_eq!(to_string(f, f32::MAX, Minus, 1), format!("34028235{:0>31}.0", ""));
assert_eq!(to_string(f, f32::MAX, Minus, 8), format!("34028235{:0>31}.00000000", ""));
#[cfg(target_has_reliable_f16)]
{
// f16
assert_eq!(to_string(f, f16::MAX, Minus, 0), "65500");
assert_eq!(to_string(f, f16::MAX, Minus, 1), "65500.0");
assert_eq!(to_string(f, f16::MAX, Minus, 8), "65500.00000000");
let minf32 = ldexp_f32(1.0, -149);
assert_eq!(to_string(f, minf32, Minus, 0), format!("0.{:0>44}1", ""));
assert_eq!(to_string(f, minf32, Minus, 45), format!("0.{:0>44}1", ""));
assert_eq!(to_string(f, minf32, Minus, 46), format!("0.{:0>44}10", ""));
let minf16 = ldexp_f16(1.0, -24);
assert_eq!(to_string(f, minf16, Minus, 0), "0.00000006");
assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006");
assert_eq!(to_string(f, minf16, Minus, 9), "0.000000060");
}
assert_eq!(to_string(f, f64::MAX, Minus, 0), format!("17976931348623157{:0>292}", ""));
assert_eq!(to_string(f, f64::MAX, Minus, 1), format!("17976931348623157{:0>292}.0", ""));
assert_eq!(to_string(f, f64::MAX, Minus, 8), format!("17976931348623157{:0>292}.00000000", ""));
{
// f32
assert_eq!(to_string(f, f32::MAX, Minus, 0), format!("34028235{:0>31}", ""));
assert_eq!(to_string(f, f32::MAX, Minus, 1), format!("34028235{:0>31}.0", ""));
assert_eq!(to_string(f, f32::MAX, Minus, 8), format!("34028235{:0>31}.00000000", ""));
let minf64 = ldexp_f64(1.0, -1074);
assert_eq!(to_string(f, minf64, Minus, 0), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, 324), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, 325), format!("0.{:0>323}50", ""));
let minf32 = ldexp_f32(1.0, -149);
assert_eq!(to_string(f, minf32, Minus, 0), format!("0.{:0>44}1", ""));
assert_eq!(to_string(f, minf32, Minus, 45), format!("0.{:0>44}1", ""));
assert_eq!(to_string(f, minf32, Minus, 46), format!("0.{:0>44}10", ""));
}
{
// f64
assert_eq!(to_string(f, f64::MAX, Minus, 0), format!("17976931348623157{:0>292}", ""));
assert_eq!(to_string(f, f64::MAX, Minus, 1), format!("17976931348623157{:0>292}.0", ""));
assert_eq!(
to_string(f, f64::MAX, Minus, 8),
format!("17976931348623157{:0>292}.00000000", "")
);
let minf64 = ldexp_f64(1.0, -1074);
assert_eq!(to_string(f, minf64, Minus, 0), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, 324), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, 325), format!("0.{:0>323}50", ""));
}
if cfg!(miri) {
// Miri is too slow
@@ -655,27 +759,45 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, exp_bounds: (i16, i16), upper: b
assert_eq!(to_string(f, 1.0e23, Minus, (23, 24), false), "100000000000000000000000");
assert_eq!(to_string(f, 1.0e23, Minus, (24, 25), false), "1e23");
assert_eq!(to_string(f, f32::MAX, Minus, (-4, 16), false), "3.4028235e38");
assert_eq!(to_string(f, f32::MAX, Minus, (-39, 38), false), "3.4028235e38");
assert_eq!(to_string(f, f32::MAX, Minus, (-38, 39), false), format!("34028235{:0>31}", ""));
#[cfg(target_has_reliable_f16)]
{
// f16
assert_eq!(to_string(f, f16::MAX, Minus, (-2, 2), false), "6.55e4");
assert_eq!(to_string(f, f16::MAX, Minus, (-4, 4), false), "6.55e4");
assert_eq!(to_string(f, f16::MAX, Minus, (-5, 5), false), "65500");
let minf32 = ldexp_f32(1.0, -149);
assert_eq!(to_string(f, minf32, Minus, (-4, 16), false), "1e-45");
assert_eq!(to_string(f, minf32, Minus, (-44, 45), false), "1e-45");
assert_eq!(to_string(f, minf32, Minus, (-45, 44), false), format!("0.{:0>44}1", ""));
let minf16 = ldexp_f16(1.0, -24);
assert_eq!(to_string(f, minf16, Minus, (-2, 2), false), "6e-8");
assert_eq!(to_string(f, minf16, Minus, (-7, 7), false), "6e-8");
assert_eq!(to_string(f, minf16, Minus, (-8, 8), false), "0.00000006");
}
assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308");
assert_eq!(
to_string(f, f64::MAX, Minus, (-308, 309), false),
format!("17976931348623157{:0>292}", "")
);
assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308");
{
// f32
assert_eq!(to_string(f, f32::MAX, Minus, (-4, 16), false), "3.4028235e38");
assert_eq!(to_string(f, f32::MAX, Minus, (-39, 38), false), "3.4028235e38");
assert_eq!(to_string(f, f32::MAX, Minus, (-38, 39), false), format!("34028235{:0>31}", ""));
let minf64 = ldexp_f64(1.0, -1074);
assert_eq!(to_string(f, minf64, Minus, (-4, 16), false), "5e-324");
assert_eq!(to_string(f, minf64, Minus, (-324, 323), false), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, (-323, 324), false), "5e-324");
let minf32 = ldexp_f32(1.0, -149);
assert_eq!(to_string(f, minf32, Minus, (-4, 16), false), "1e-45");
assert_eq!(to_string(f, minf32, Minus, (-44, 45), false), "1e-45");
assert_eq!(to_string(f, minf32, Minus, (-45, 44), false), format!("0.{:0>44}1", ""));
}
{
// f64
assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308");
assert_eq!(
to_string(f, f64::MAX, Minus, (-308, 309), false),
format!("17976931348623157{:0>292}", "")
);
assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308");
let minf64 = ldexp_f64(1.0, -1074);
assert_eq!(to_string(f, minf64, Minus, (-4, 16), false), "5e-324");
assert_eq!(to_string(f, minf64, Minus, (-324, 323), false), format!("0.{:0>323}5", ""));
assert_eq!(to_string(f, minf64, Minus, (-323, 324), false), "5e-324");
}
assert_eq!(to_string(f, 1.1, Minus, (i16::MIN, i16::MAX), false), "1.1");
}
@@ -791,6 +913,26 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, ndigits: usize, upper: bool) ->
"9.999999999999999547481118258862586856139387236908078193664550781250000e-7"
);
#[cfg(target_has_reliable_f16)]
{
assert_eq!(to_string(f, f16::MAX, Minus, 1, false), "7e4");
assert_eq!(to_string(f, f16::MAX, Minus, 2, false), "6.6e4");
assert_eq!(to_string(f, f16::MAX, Minus, 4, false), "6.550e4");
assert_eq!(to_string(f, f16::MAX, Minus, 5, false), "6.5504e4");
assert_eq!(to_string(f, f16::MAX, Minus, 6, false), "6.55040e4");
assert_eq!(to_string(f, f16::MAX, Minus, 16, false), "6.550400000000000e4");
let minf16 = ldexp_f16(1.0, -24);
assert_eq!(to_string(f, minf16, Minus, 1, false), "6e-8");
assert_eq!(to_string(f, minf16, Minus, 2, false), "6.0e-8");
assert_eq!(to_string(f, minf16, Minus, 4, false), "5.960e-8");
assert_eq!(to_string(f, minf16, Minus, 8, false), "5.9604645e-8");
assert_eq!(to_string(f, minf16, Minus, 16, false), "5.960464477539062e-8");
assert_eq!(to_string(f, minf16, Minus, 17, false), "5.9604644775390625e-8");
assert_eq!(to_string(f, minf16, Minus, 18, false), "5.96046447753906250e-8");
assert_eq!(to_string(f, minf16, Minus, 24, false), "5.96046447753906250000000e-8");
}
assert_eq!(to_string(f, f32::MAX, Minus, 1, false), "3e38");
assert_eq!(to_string(f, f32::MAX, Minus, 2, false), "3.4e38");
assert_eq!(to_string(f, f32::MAX, Minus, 4, false), "3.403e38");
@@ -1069,6 +1211,13 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
"0.000000999999999999999954748111825886258685613938723690807819366455078125000"
);
#[cfg(target_has_reliable_f16)]
{
assert_eq!(to_string(f, f16::MAX, Minus, 0), "65504");
assert_eq!(to_string(f, f16::MAX, Minus, 1), "65504.0");
assert_eq!(to_string(f, f16::MAX, Minus, 2), "65504.00");
}
assert_eq!(to_string(f, f32::MAX, Minus, 0), "340282346638528859811704183484516925440");
assert_eq!(to_string(f, f32::MAX, Minus, 1), "340282346638528859811704183484516925440.0");
assert_eq!(to_string(f, f32::MAX, Minus, 2), "340282346638528859811704183484516925440.00");
@@ -1078,6 +1227,21 @@ fn to_string<T, F>(f: &mut F, v: T, sign: Sign, frac_digits: usize) -> String
return;
}
#[cfg(target_has_reliable_f16)]
{
let minf16 = ldexp_f16(1.0, -24);
assert_eq!(to_string(f, minf16, Minus, 0), "0");
assert_eq!(to_string(f, minf16, Minus, 1), "0.0");
assert_eq!(to_string(f, minf16, Minus, 2), "0.00");
assert_eq!(to_string(f, minf16, Minus, 4), "0.0000");
assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006");
assert_eq!(to_string(f, minf16, Minus, 10), "0.0000000596");
assert_eq!(to_string(f, minf16, Minus, 15), "0.000000059604645");
assert_eq!(to_string(f, minf16, Minus, 20), "0.00000005960464477539");
assert_eq!(to_string(f, minf16, Minus, 24), "0.000000059604644775390625");
assert_eq!(to_string(f, minf16, Minus, 32), "0.00000005960464477539062500000000");
}
let minf32 = ldexp_f32(1.0, -149);
assert_eq!(to_string(f, minf32, Minus, 0), "0");
assert_eq!(to_string(f, minf32, Minus, 1), "0.0");
@@ -79,6 +79,20 @@ fn iterate<F, G, V>(func: &str, k: usize, n: usize, mut f: F, mut g: G, mut v: V
(npassed, nignored)
}
#[cfg(target_has_reliable_f16)]
pub fn f16_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
let mut rng = crate::test_rng();
let f16_range = Uniform::new(0x0001u16, 0x7c00).unwrap();
iterate("f16_random_equivalence_test", k, n, f, g, |_| {
let x = f16::from_bits(f16_range.sample(&mut rng));
decode_finite(x)
});
}
pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
@@ -105,6 +119,24 @@ pub fn f64_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
});
}
#[cfg(target_has_reliable_f16)]
pub fn f16_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
{
// Unlike the other float types, `f16` is small enough that these exhaustive tests
// can run in less than a second so we don't need to ignore it.
// iterate from 0x0001 to 0x7bff, i.e., all finite ranges
let (npassed, nignored) =
iterate("f16_exhaustive_equivalence_test", k, 0x7bff, f, g, |i: usize| {
let x = f16::from_bits(i as u16 + 1);
decode_finite(x)
});
assert_eq!((npassed, nignored), (29735, 2008));
}
pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
where
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
@@ -133,6 +165,17 @@ fn shortest_random_equivalence_test() {
f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
f32_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
#[cfg(target_has_reliable_f16)]
f16_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
}
#[test]
#[cfg_attr(miri, ignore)] // Miri is to slow
#[cfg(target_has_reliable_f16)]
fn shortest_f16_exhaustive_equivalence_test() {
// see the f32 version
use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
f16_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS);
}
#[test]
@@ -158,6 +201,23 @@ fn shortest_f64_hard_random_equivalence_test() {
f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, 100_000_000);
}
#[test]
#[cfg(target_has_reliable_f16)]
fn exact_f16_random_equivalence_test() {
use core::num::flt2dec::strategy::dragon::format_exact as fallback;
// Miri is too slow
let n = if cfg!(miri) { 3 } else { 1_000 };
for k in 1..21 {
f16_random_equivalence_test(
|d, buf| format_exact_opt(d, buf, i16::MIN),
|d, buf| fallback(d, buf, i16::MIN),
k,
n,
);
}
}
#[test]
fn exact_f32_random_equivalence_test() {
use core::num::flt2dec::strategy::dragon::format_exact as fallback;
@@ -18,6 +18,8 @@ fn test_mul_pow10() {
fn shortest_sanity_test() {
f64_shortest_sanity_test(format_shortest);
f32_shortest_sanity_test(format_shortest);
#[cfg(target_has_reliable_f16)]
f16_shortest_sanity_test(format_shortest);
more_shortest_sanity_test(format_shortest);
}
@@ -41,6 +43,9 @@ fn exact_sanity_test() {
f64_exact_sanity_test(format_exact);
}
f32_exact_sanity_test(format_exact);
#[cfg(target_has_reliable_f16)]
f16_exact_sanity_test(format_exact);
}
#[test]
@@ -38,6 +38,8 @@ fn test_max_pow10_no_more_than() {
fn shortest_sanity_test() {
f64_shortest_sanity_test(format_shortest);
f32_shortest_sanity_test(format_shortest);
#[cfg(target_has_reliable_f16)]
f16_shortest_sanity_test(format_shortest);
more_shortest_sanity_test(format_shortest);
}
@@ -50,6 +52,8 @@ fn exact_sanity_test() {
f64_exact_sanity_test(format_exact);
}
f32_exact_sanity_test(format_exact);
#[cfg(target_has_reliable_f16)]
f16_exact_sanity_test(format_exact);
}
#[test]