Skip to content

Commit 977d841

Browse files
tgross35speedy-lex
andcommittedMay 18, 2025
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>
1 parent 6fc60b8 commit 977d841

File tree

9 files changed

+501
-77
lines changed

9 files changed

+501
-77
lines changed
 

‎library/coretests/tests/num/dec2flt/decimal.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ const FPATHS_F32: &[FPath<f32>] =
77
const FPATHS_F64: &[FPath<f64>] =
88
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
99

10+
// FIXME(f16_f128): enable on all targets once possible.
11+
#[test]
12+
#[cfg(target_has_reliable_f16)]
13+
fn check_fast_path_f16() {
14+
const FPATHS_F16: &[FPath<f16>] =
15+
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
16+
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F16.iter().copied() {
17+
let dec = Decimal { exponent, mantissa, negative, many_digits };
18+
let actual = dec.try_fast_path::<f16>();
19+
20+
assert_eq!(actual, expected);
21+
}
22+
}
23+
1024
#[test]
1125
fn check_fast_path_f32() {
1226
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F32.iter().copied() {

‎library/coretests/tests/num/dec2flt/float.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
use core::num::dec2flt::float::RawFloat;
22

3+
// FIXME(f16_f128): enable on all targets once possible.
4+
#[test]
5+
#[cfg(target_has_reliable_f16)]
6+
fn test_f16_integer_decode() {
7+
assert_eq!(3.14159265359f16.integer_decode(), (1608, -9, 1));
8+
assert_eq!((-8573.5918555f16).integer_decode(), (1072, 3, -1));
9+
#[cfg(not(miri))] // miri doesn't have powf16
10+
assert_eq!(2f16.powf(14.0).integer_decode(), (1 << 10, 4, 1));
11+
assert_eq!(0f16.integer_decode(), (0, -25, 1));
12+
assert_eq!((-0f16).integer_decode(), (0, -25, -1));
13+
assert_eq!(f16::INFINITY.integer_decode(), (1 << 10, 6, 1));
14+
assert_eq!(f16::NEG_INFINITY.integer_decode(), (1 << 10, 6, -1));
15+
16+
// Ignore the "sign" (quiet / signalling flag) of NAN.
17+
// It can vary between runtime operations and LLVM folding.
18+
let (nan_m, nan_p, _nan_s) = f16::NAN.integer_decode();
19+
assert_eq!((nan_m, nan_p), (1536, 6));
20+
}
21+
322
#[test]
423
fn test_f32_integer_decode() {
524
assert_eq!(3.14159265359f32.integer_decode(), (13176795, -22, 1));
@@ -34,6 +53,27 @@ fn test_f64_integer_decode() {
3453

3554
/* Sanity checks of computed magic numbers */
3655

56+
// FIXME(f16_f128): enable on all targets once possible.
57+
#[test]
58+
#[cfg(target_has_reliable_f16)]
59+
fn test_f16_consts() {
60+
assert_eq!(<f16 as RawFloat>::INFINITY, f16::INFINITY);
61+
assert_eq!(<f16 as RawFloat>::NEG_INFINITY, -f16::INFINITY);
62+
assert_eq!(<f16 as RawFloat>::NAN.to_bits(), f16::NAN.to_bits());
63+
assert_eq!(<f16 as RawFloat>::NEG_NAN.to_bits(), (-f16::NAN).to_bits());
64+
assert_eq!(<f16 as RawFloat>::SIG_BITS, 10);
65+
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -22);
66+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 5);
67+
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_FAST_PATH, -4);
68+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_FAST_PATH, 4);
69+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 7);
70+
assert_eq!(<f16 as RawFloat>::EXP_MIN, -14);
71+
assert_eq!(<f16 as RawFloat>::EXP_SAT, 0x1f);
72+
assert_eq!(<f16 as RawFloat>::SMALLEST_POWER_OF_TEN, -27);
73+
assert_eq!(<f16 as RawFloat>::LARGEST_POWER_OF_TEN, 4);
74+
assert_eq!(<f16 as RawFloat>::MAX_MANTISSA_FAST_PATH, 2048);
75+
}
76+
3777
#[test]
3878
fn test_f32_consts() {
3979
assert_eq!(<f32 as RawFloat>::INFINITY, f32::INFINITY);
Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
use core::num::dec2flt::float::RawFloat;
22
use core::num::dec2flt::lemire::compute_float;
33

4+
#[cfg(target_has_reliable_f16)]
5+
fn compute_float16(q: i64, w: u64) -> (i32, u64) {
6+
let fp = compute_float::<f16>(q, w);
7+
(fp.p_biased, fp.m)
8+
}
9+
410
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
511
let fp = compute_float::<f32>(q, w);
612
(fp.p_biased, fp.m)
@@ -11,23 +17,73 @@ fn compute_float64(q: i64, w: u64) -> (i32, u64) {
1117
(fp.p_biased, fp.m)
1218
}
1319

20+
// FIXME(f16_f128): enable on all targets once possible.
21+
#[test]
22+
#[cfg(target_has_reliable_f16)]
23+
fn compute_float_f16_rounding() {
24+
// The maximum integer that cna be converted to a `f16` without lost precision.
25+
let val = 1 << 11;
26+
let scale = 10_u64.pow(10);
27+
28+
// These test near-halfway cases for half-precision floats.
29+
assert_eq!(compute_float16(0, val), (26, 0));
30+
assert_eq!(compute_float16(0, val + 1), (26, 0));
31+
assert_eq!(compute_float16(0, val + 2), (26, 1));
32+
assert_eq!(compute_float16(0, val + 3), (26, 2));
33+
assert_eq!(compute_float16(0, val + 4), (26, 2));
34+
35+
// For the next power up, the two nearest representable numbers are twice as far apart.
36+
let val2 = 1 << 12;
37+
assert_eq!(compute_float16(0, val2), (27, 0));
38+
assert_eq!(compute_float16(0, val2 + 2), (27, 0));
39+
assert_eq!(compute_float16(0, val2 + 4), (27, 1));
40+
assert_eq!(compute_float16(0, val2 + 6), (27, 2));
41+
assert_eq!(compute_float16(0, val2 + 8), (27, 2));
42+
43+
// These are examples of the above tests, with digits from the exponent shifted
44+
// to the mantissa.
45+
assert_eq!(compute_float16(-10, val * scale), (26, 0));
46+
assert_eq!(compute_float16(-10, (val + 1) * scale), (26, 0));
47+
assert_eq!(compute_float16(-10, (val + 2) * scale), (26, 1));
48+
// Let's check the lines to see if anything is different in table...
49+
assert_eq!(compute_float16(-10, (val + 3) * scale), (26, 2));
50+
assert_eq!(compute_float16(-10, (val + 4) * scale), (26, 2));
51+
52+
// Check the rounding point between infinity and the next representable number down
53+
assert_eq!(compute_float16(4, 6), (f16::INFINITE_POWER - 1, 851));
54+
assert_eq!(compute_float16(4, 7), (f16::INFINITE_POWER, 0)); // infinity
55+
assert_eq!(compute_float16(2, 655), (f16::INFINITE_POWER - 1, 1023));
56+
}
57+
1458
#[test]
1559
fn compute_float_f32_rounding() {
60+
// the maximum integer that cna be converted to a `f32` without lost precision.
61+
let val = 1 << 24;
62+
let scale = 10_u64.pow(10);
63+
1664
// These test near-halfway cases for single-precision floats.
17-
assert_eq!(compute_float32(0, 16777216), (151, 0));
18-
assert_eq!(compute_float32(0, 16777217), (151, 0));
19-
assert_eq!(compute_float32(0, 16777218), (151, 1));
20-
assert_eq!(compute_float32(0, 16777219), (151, 2));
21-
assert_eq!(compute_float32(0, 16777220), (151, 2));
22-
23-
// These are examples of the above tests, with
24-
// digits from the exponent shifted to the mantissa.
25-
assert_eq!(compute_float32(-10, 167772160000000000), (151, 0));
26-
assert_eq!(compute_float32(-10, 167772170000000000), (151, 0));
27-
assert_eq!(compute_float32(-10, 167772180000000000), (151, 1));
65+
assert_eq!(compute_float32(0, val), (151, 0));
66+
assert_eq!(compute_float32(0, val + 1), (151, 0));
67+
assert_eq!(compute_float32(0, val + 2), (151, 1));
68+
assert_eq!(compute_float32(0, val + 3), (151, 2));
69+
assert_eq!(compute_float32(0, val + 4), (151, 2));
70+
71+
// For the next power up, the two nearest representable numbers are twice as far apart.
72+
let val2 = 1 << 25;
73+
assert_eq!(compute_float32(0, val2), (152, 0));
74+
assert_eq!(compute_float32(0, val2 + 2), (152, 0));
75+
assert_eq!(compute_float32(0, val2 + 4), (152, 1));
76+
assert_eq!(compute_float32(0, val2 + 6), (152, 2));
77+
assert_eq!(compute_float32(0, val2 + 8), (152, 2));
78+
79+
// These are examples of the above tests, with digits from the exponent shifted
80+
// to the mantissa.
81+
assert_eq!(compute_float32(-10, val * scale), (151, 0));
82+
assert_eq!(compute_float32(-10, (val + 1) * scale), (151, 0));
83+
assert_eq!(compute_float32(-10, (val + 2) * scale), (151, 1));
2884
// Let's check the lines to see if anything is different in table...
29-
assert_eq!(compute_float32(-10, 167772190000000000), (151, 2));
30-
assert_eq!(compute_float32(-10, 167772200000000000), (151, 2));
85+
assert_eq!(compute_float32(-10, (val + 3) * scale), (151, 2));
86+
assert_eq!(compute_float32(-10, (val + 4) * scale), (151, 2));
3187

3288
// Check the rounding point between infinity and the next representable number down
3389
assert_eq!(compute_float32(38, 3), (f32::INFINITE_POWER - 1, 6402534));
@@ -37,23 +93,38 @@ fn compute_float_f32_rounding() {
3793

3894
#[test]
3995
fn compute_float_f64_rounding() {
96+
// The maximum integer that cna be converted to a `f64` without lost precision.
97+
let val = 1 << 53;
98+
let scale = 1000;
99+
40100
// These test near-halfway cases for double-precision floats.
41-
assert_eq!(compute_float64(0, 9007199254740992), (1076, 0));
42-
assert_eq!(compute_float64(0, 9007199254740993), (1076, 0));
43-
assert_eq!(compute_float64(0, 9007199254740994), (1076, 1));
44-
assert_eq!(compute_float64(0, 9007199254740995), (1076, 2));
45-
assert_eq!(compute_float64(0, 9007199254740996), (1076, 2));
46-
assert_eq!(compute_float64(0, 18014398509481984), (1077, 0));
47-
assert_eq!(compute_float64(0, 18014398509481986), (1077, 0));
48-
assert_eq!(compute_float64(0, 18014398509481988), (1077, 1));
49-
assert_eq!(compute_float64(0, 18014398509481990), (1077, 2));
50-
assert_eq!(compute_float64(0, 18014398509481992), (1077, 2));
51-
52-
// These are examples of the above tests, with
53-
// digits from the exponent shifted to the mantissa.
54-
assert_eq!(compute_float64(-3, 9007199254740992000), (1076, 0));
55-
assert_eq!(compute_float64(-3, 9007199254740993000), (1076, 0));
56-
assert_eq!(compute_float64(-3, 9007199254740994000), (1076, 1));
57-
assert_eq!(compute_float64(-3, 9007199254740995000), (1076, 2));
58-
assert_eq!(compute_float64(-3, 9007199254740996000), (1076, 2));
101+
assert_eq!(compute_float64(0, val), (1076, 0));
102+
assert_eq!(compute_float64(0, val + 1), (1076, 0));
103+
assert_eq!(compute_float64(0, val + 2), (1076, 1));
104+
assert_eq!(compute_float64(0, val + 3), (1076, 2));
105+
assert_eq!(compute_float64(0, val + 4), (1076, 2));
106+
107+
// For the next power up, the two nearest representable numbers are twice as far apart.
108+
let val2 = 1 << 54;
109+
assert_eq!(compute_float64(0, val2), (1077, 0));
110+
assert_eq!(compute_float64(0, val2 + 2), (1077, 0));
111+
assert_eq!(compute_float64(0, val2 + 4), (1077, 1));
112+
assert_eq!(compute_float64(0, val2 + 6), (1077, 2));
113+
assert_eq!(compute_float64(0, val2 + 8), (1077, 2));
114+
115+
// These are examples of the above tests, with digits from the exponent shifted
116+
// to the mantissa.
117+
assert_eq!(compute_float64(-3, val * scale), (1076, 0));
118+
assert_eq!(compute_float64(-3, (val + 1) * scale), (1076, 0));
119+
assert_eq!(compute_float64(-3, (val + 2) * scale), (1076, 1));
120+
assert_eq!(compute_float64(-3, (val + 3) * scale), (1076, 2));
121+
assert_eq!(compute_float64(-3, (val + 4) * scale), (1076, 2));
122+
123+
// Check the rounding point between infinity and the next representable number down
124+
assert_eq!(compute_float64(308, 1), (f64::INFINITE_POWER - 1, 506821272651936));
125+
assert_eq!(compute_float64(308, 2), (f64::INFINITE_POWER, 0)); // infinity
126+
assert_eq!(
127+
compute_float64(292, 17976931348623157),
128+
(f64::INFINITE_POWER - 1, 4503599627370495)
129+
);
59130
}

‎library/coretests/tests/num/dec2flt/mod.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@ mod parse;
1111
// Requires a *polymorphic literal*, i.e., one that can serve as f64 as well as f32.
1212
macro_rules! test_literal {
1313
($x: expr) => {{
14+
#[cfg(target_has_reliable_f16)]
15+
let x16: f16 = $x;
1416
let x32: f32 = $x;
1517
let x64: f64 = $x;
1618
let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
19+
1720
for input in inputs {
18-
assert_eq!(input.parse(), Ok(x64));
19-
assert_eq!(input.parse(), Ok(x32));
21+
assert_eq!(input.parse(), Ok(x64), "failed f64 {input}");
22+
assert_eq!(input.parse(), Ok(x32), "failed f32 {input}");
23+
#[cfg(target_has_reliable_f16)]
24+
assert_eq!(input.parse(), Ok(x16), "failed f16 {input}");
25+
2026
let neg_input = format!("-{input}");
21-
assert_eq!(neg_input.parse(), Ok(-x64));
22-
assert_eq!(neg_input.parse(), Ok(-x32));
27+
assert_eq!(neg_input.parse(), Ok(-x64), "failed f64 {neg_input}");
28+
assert_eq!(neg_input.parse(), Ok(-x32), "failed f32 {neg_input}");
29+
#[cfg(target_has_reliable_f16)]
30+
assert_eq!(neg_input.parse(), Ok(-x16), "failed f16 {neg_input}");
2331
}
2432
}};
2533
}
@@ -84,48 +92,87 @@ fn fast_path_correct() {
8492
test_literal!(1.448997445238699);
8593
}
8694

95+
// FIXME(f16_f128): remove gates once tests work on all targets
96+
8797
#[test]
8898
fn lonely_dot() {
99+
#[cfg(target_has_reliable_f16)]
100+
assert!(".".parse::<f16>().is_err());
89101
assert!(".".parse::<f32>().is_err());
90102
assert!(".".parse::<f64>().is_err());
91103
}
92104

93105
#[test]
94106
fn exponentiated_dot() {
107+
#[cfg(target_has_reliable_f16)]
108+
assert!(".e0".parse::<f16>().is_err());
95109
assert!(".e0".parse::<f32>().is_err());
96110
assert!(".e0".parse::<f64>().is_err());
97111
}
98112

99113
#[test]
100114
fn lonely_sign() {
101-
assert!("+".parse::<f32>().is_err());
102-
assert!("-".parse::<f64>().is_err());
115+
#[cfg(target_has_reliable_f16)]
116+
assert!("+".parse::<f16>().is_err());
117+
assert!("-".parse::<f32>().is_err());
118+
assert!("+".parse::<f64>().is_err());
103119
}
104120

105121
#[test]
106122
fn whitespace() {
123+
#[cfg(target_has_reliable_f16)]
124+
assert!("1.0 ".parse::<f16>().is_err());
107125
assert!(" 1.0".parse::<f32>().is_err());
108126
assert!("1.0 ".parse::<f64>().is_err());
109127
}
110128

111129
#[test]
112130
fn nan() {
131+
#[cfg(target_has_reliable_f16)]
132+
{
133+
assert!("NaN".parse::<f16>().unwrap().is_nan());
134+
assert!("-NaN".parse::<f16>().unwrap().is_nan());
135+
}
136+
113137
assert!("NaN".parse::<f32>().unwrap().is_nan());
138+
assert!("-NaN".parse::<f32>().unwrap().is_nan());
139+
114140
assert!("NaN".parse::<f64>().unwrap().is_nan());
141+
assert!("-NaN".parse::<f64>().unwrap().is_nan());
115142
}
116143

117144
#[test]
118145
fn inf() {
119-
assert_eq!("inf".parse(), Ok(f64::INFINITY));
120-
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
146+
#[cfg(target_has_reliable_f16)]
147+
{
148+
assert_eq!("inf".parse(), Ok(f16::INFINITY));
149+
assert_eq!("-inf".parse(), Ok(f16::NEG_INFINITY));
150+
}
151+
121152
assert_eq!("inf".parse(), Ok(f32::INFINITY));
122153
assert_eq!("-inf".parse(), Ok(f32::NEG_INFINITY));
154+
155+
assert_eq!("inf".parse(), Ok(f64::INFINITY));
156+
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
123157
}
124158

125159
#[test]
126160
fn massive_exponent() {
161+
#[cfg(target_has_reliable_f16)]
162+
{
163+
let max = i16::MAX;
164+
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
165+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f16));
166+
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
167+
}
168+
169+
let max = i32::MAX;
170+
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
171+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f32));
172+
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
173+
127174
let max = i64::MAX;
128175
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
129-
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0));
176+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f64));
130177
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
131178
}

‎library/coretests/tests/num/dec2flt/parse.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,41 @@ fn new_dec(e: i64, m: u64) -> Decimal {
1010
fn missing_pieces() {
1111
let permutations = &[".e", "1e", "e4", "e", ".12e", "321.e", "32.12e+", "12.32e-"];
1212
for &s in permutations {
13+
#[cfg(target_has_reliable_f16)]
14+
assert_eq!(dec2flt::<f16>(s), Err(pfe_invalid()));
15+
assert_eq!(dec2flt::<f32>(s), Err(pfe_invalid()));
1316
assert_eq!(dec2flt::<f64>(s), Err(pfe_invalid()));
1417
}
1518
}
1619

1720
#[test]
1821
fn invalid_chars() {
1922
let invalid = "r,?<j";
20-
let error = Err(pfe_invalid());
2123
let valid_strings = &["123", "666.", ".1", "5e1", "7e-3", "0.0e+1"];
24+
2225
for c in invalid.chars() {
2326
for s in valid_strings {
2427
for i in 0..s.len() {
2528
let mut input = String::new();
2629
input.push_str(s);
2730
input.insert(i, c);
28-
assert!(dec2flt::<f64>(&input) == error, "did not reject invalid {:?}", input);
31+
32+
#[cfg(target_has_reliable_f16)]
33+
assert_eq!(
34+
dec2flt::<f16>(&input),
35+
Err(pfe_invalid()),
36+
"f16 did not reject invalid {input:?}",
37+
);
38+
assert_eq!(
39+
dec2flt::<f32>(&input),
40+
Err(pfe_invalid()),
41+
"f32 did not reject invalid {input:?}",
42+
);
43+
assert_eq!(
44+
dec2flt::<f64>(&input),
45+
Err(pfe_invalid()),
46+
"f64 did not reject invalid {input:?}",
47+
);
2948
}
3049
}
3150
}

‎library/coretests/tests/num/flt2dec/mod.rs

Lines changed: 199 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ mod random;
1616
pub fn decode_finite<T: DecodableFloat>(v: T) -> Decoded {
1717
match decode(v).1 {
1818
FullDecoded::Finite(decoded) => decoded,
19-
full_decoded => panic!("expected finite, got {full_decoded:?} instead"),
19+
full_decoded => panic!("expected finite, got {full_decoded:?} instead for {v:?}"),
2020
}
2121
}
2222

@@ -75,6 +75,11 @@ macro_rules! try_fixed {
7575
})
7676
}
7777

78+
#[cfg(target_has_reliable_f16)]
79+
fn ldexp_f16(a: f16, b: i32) -> f16 {
80+
ldexp_f64(a as f64, b) as f16
81+
}
82+
7883
fn ldexp_f32(a: f32, b: i32) -> f32 {
7984
ldexp_f64(a as f64, b) as f32
8085
}
@@ -176,6 +181,13 @@ trait TestableFloat: DecodableFloat + fmt::Display {
176181
fn ldexpi(f: i64, exp: isize) -> Self;
177182
}
178183

184+
#[cfg(target_has_reliable_f16)]
185+
impl TestableFloat for f16 {
186+
fn ldexpi(f: i64, exp: isize) -> Self {
187+
f as Self * (exp as Self).exp2()
188+
}
189+
}
190+
179191
impl TestableFloat for f32 {
180192
fn ldexpi(f: i64, exp: isize) -> Self {
181193
f as Self * (exp as Self).exp2()
@@ -225,6 +237,76 @@ macro_rules! check_exact_one {
225237
//
226238
// [1] Vern Paxson, A Program for Testing IEEE Decimal-Binary Conversion
227239
// ftp://ftp.ee.lbl.gov/testbase-report.ps.Z
240+
// or https://www.icir.org/vern/papers/testbase-report.pdf
241+
242+
#[cfg(target_has_reliable_f16)]
243+
pub fn f16_shortest_sanity_test<F>(mut f: F)
244+
where
245+
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
246+
{
247+
// 0.0999145507813
248+
// 0.0999755859375
249+
// 0.100036621094
250+
check_shortest!(f(0.1f16) => b"1", 0);
251+
252+
// 0.3330078125
253+
// 0.333251953125 (1/3 in the default rounding)
254+
// 0.33349609375
255+
check_shortest!(f(1.0f16/3.0) => b"3333", 0);
256+
257+
// 10^1 * 0.3138671875
258+
// 10^1 * 0.3140625
259+
// 10^1 * 0.3142578125
260+
check_shortest!(f(3.14f16) => b"314", 1);
261+
262+
// 10^18 * 0.31415916243714048
263+
// 10^18 * 0.314159196796878848
264+
// 10^18 * 0.314159231156617216
265+
check_shortest!(f(3.1415e4f16) => b"3141", 5);
266+
267+
// regression test for decoders
268+
// 10^2 * 0.31984375
269+
// 10^2 * 0.32
270+
// 10^2 * 0.3203125
271+
check_shortest!(f(ldexp_f16(1.0, 5)) => b"32", 2);
272+
273+
// 10^5 * 0.65472
274+
// 10^5 * 0.65504
275+
// 10^5 * 0.65536
276+
check_shortest!(f(f16::MAX) => b"655", 5);
277+
278+
// 10^-4 * 0.60975551605224609375
279+
// 10^-4 * 0.6103515625
280+
// 10^-4 * 0.61094760894775390625
281+
check_shortest!(f(f16::MIN_POSITIVE) => b"6104", -4);
282+
283+
// 10^-9 * 0
284+
// 10^-9 * 0.59604644775390625
285+
// 10^-8 * 0.11920928955078125
286+
let minf16 = ldexp_f16(1.0, -24);
287+
check_shortest!(f(minf16) => b"6", -7);
288+
}
289+
290+
#[cfg(target_has_reliable_f16)]
291+
pub fn f16_exact_sanity_test<F>(mut f: F)
292+
where
293+
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>], i16) -> (&'a [u8], i16),
294+
{
295+
let minf16 = ldexp_f16(1.0, -24);
296+
297+
check_exact!(f(0.1f16) => b"999755859375 ", -1);
298+
check_exact!(f(0.5f16) => b"5 ", 0);
299+
check_exact!(f(1.0f16/3.0) => b"333251953125 ", 0);
300+
check_exact!(f(3.141f16) => b"3140625 ", 1);
301+
check_exact!(f(3.141e4f16) => b"31408 ", 5);
302+
check_exact!(f(f16::MAX) => b"65504 ", 5);
303+
check_exact!(f(f16::MIN_POSITIVE) => b"6103515625 ", -4);
304+
check_exact!(f(minf16) => b"59604644775390625", -7);
305+
306+
// FIXME(f16_f128): these should gain the check_exact_one tests like `f32` and `f64` have,
307+
// but these values are not easy to generate. The algorithm from the Paxon paper [1] needs
308+
// to be adapted to binary16.
309+
}
228310

229311
pub fn f32_shortest_sanity_test<F>(mut f: F)
230312
where
@@ -553,23 +635,45 @@ where
553635
assert_eq!(to_string(f, 1.9971e20, Minus, 1), "199710000000000000000.0");
554636
assert_eq!(to_string(f, 1.9971e20, Minus, 8), "199710000000000000000.00000000");
555637

556-
assert_eq!(to_string(f, f32::MAX, Minus, 0), format!("34028235{:0>31}", ""));
557-
assert_eq!(to_string(f, f32::MAX, Minus, 1), format!("34028235{:0>31}.0", ""));
558-
assert_eq!(to_string(f, f32::MAX, Minus, 8), format!("34028235{:0>31}.00000000", ""));
559-
560-
let minf32 = ldexp_f32(1.0, -149);
561-
assert_eq!(to_string(f, minf32, Minus, 0), format!("0.{:0>44}1", ""));
562-
assert_eq!(to_string(f, minf32, Minus, 45), format!("0.{:0>44}1", ""));
563-
assert_eq!(to_string(f, minf32, Minus, 46), format!("0.{:0>44}10", ""));
638+
#[cfg(target_has_reliable_f16)]
639+
{
640+
// f16
641+
assert_eq!(to_string(f, f16::MAX, Minus, 0), "65500");
642+
assert_eq!(to_string(f, f16::MAX, Minus, 1), "65500.0");
643+
assert_eq!(to_string(f, f16::MAX, Minus, 8), "65500.00000000");
644+
645+
let minf16 = ldexp_f16(1.0, -24);
646+
assert_eq!(to_string(f, minf16, Minus, 0), "0.00000006");
647+
assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006");
648+
assert_eq!(to_string(f, minf16, Minus, 9), "0.000000060");
649+
}
564650

565-
assert_eq!(to_string(f, f64::MAX, Minus, 0), format!("17976931348623157{:0>292}", ""));
566-
assert_eq!(to_string(f, f64::MAX, Minus, 1), format!("17976931348623157{:0>292}.0", ""));
567-
assert_eq!(to_string(f, f64::MAX, Minus, 8), format!("17976931348623157{:0>292}.00000000", ""));
651+
{
652+
// f32
653+
assert_eq!(to_string(f, f32::MAX, Minus, 0), format!("34028235{:0>31}", ""));
654+
assert_eq!(to_string(f, f32::MAX, Minus, 1), format!("34028235{:0>31}.0", ""));
655+
assert_eq!(to_string(f, f32::MAX, Minus, 8), format!("34028235{:0>31}.00000000", ""));
656+
657+
let minf32 = ldexp_f32(1.0, -149);
658+
assert_eq!(to_string(f, minf32, Minus, 0), format!("0.{:0>44}1", ""));
659+
assert_eq!(to_string(f, minf32, Minus, 45), format!("0.{:0>44}1", ""));
660+
assert_eq!(to_string(f, minf32, Minus, 46), format!("0.{:0>44}10", ""));
661+
}
568662

569-
let minf64 = ldexp_f64(1.0, -1074);
570-
assert_eq!(to_string(f, minf64, Minus, 0), format!("0.{:0>323}5", ""));
571-
assert_eq!(to_string(f, minf64, Minus, 324), format!("0.{:0>323}5", ""));
572-
assert_eq!(to_string(f, minf64, Minus, 325), format!("0.{:0>323}50", ""));
663+
{
664+
// f64
665+
assert_eq!(to_string(f, f64::MAX, Minus, 0), format!("17976931348623157{:0>292}", ""));
666+
assert_eq!(to_string(f, f64::MAX, Minus, 1), format!("17976931348623157{:0>292}.0", ""));
667+
assert_eq!(
668+
to_string(f, f64::MAX, Minus, 8),
669+
format!("17976931348623157{:0>292}.00000000", "")
670+
);
671+
672+
let minf64 = ldexp_f64(1.0, -1074);
673+
assert_eq!(to_string(f, minf64, Minus, 0), format!("0.{:0>323}5", ""));
674+
assert_eq!(to_string(f, minf64, Minus, 324), format!("0.{:0>323}5", ""));
675+
assert_eq!(to_string(f, minf64, Minus, 325), format!("0.{:0>323}50", ""));
676+
}
573677

574678
if cfg!(miri) {
575679
// Miri is too slow
@@ -655,27 +759,45 @@ where
655759
assert_eq!(to_string(f, 1.0e23, Minus, (23, 24), false), "100000000000000000000000");
656760
assert_eq!(to_string(f, 1.0e23, Minus, (24, 25), false), "1e23");
657761

658-
assert_eq!(to_string(f, f32::MAX, Minus, (-4, 16), false), "3.4028235e38");
659-
assert_eq!(to_string(f, f32::MAX, Minus, (-39, 38), false), "3.4028235e38");
660-
assert_eq!(to_string(f, f32::MAX, Minus, (-38, 39), false), format!("34028235{:0>31}", ""));
661-
662-
let minf32 = ldexp_f32(1.0, -149);
663-
assert_eq!(to_string(f, minf32, Minus, (-4, 16), false), "1e-45");
664-
assert_eq!(to_string(f, minf32, Minus, (-44, 45), false), "1e-45");
665-
assert_eq!(to_string(f, minf32, Minus, (-45, 44), false), format!("0.{:0>44}1", ""));
666-
667-
assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308");
668-
assert_eq!(
669-
to_string(f, f64::MAX, Minus, (-308, 309), false),
670-
format!("17976931348623157{:0>292}", "")
671-
);
672-
assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308");
762+
#[cfg(target_has_reliable_f16)]
763+
{
764+
// f16
765+
assert_eq!(to_string(f, f16::MAX, Minus, (-2, 2), false), "6.55e4");
766+
assert_eq!(to_string(f, f16::MAX, Minus, (-4, 4), false), "6.55e4");
767+
assert_eq!(to_string(f, f16::MAX, Minus, (-5, 5), false), "65500");
768+
769+
let minf16 = ldexp_f16(1.0, -24);
770+
assert_eq!(to_string(f, minf16, Minus, (-2, 2), false), "6e-8");
771+
assert_eq!(to_string(f, minf16, Minus, (-7, 7), false), "6e-8");
772+
assert_eq!(to_string(f, minf16, Minus, (-8, 8), false), "0.00000006");
773+
}
673774

674-
let minf64 = ldexp_f64(1.0, -1074);
675-
assert_eq!(to_string(f, minf64, Minus, (-4, 16), false), "5e-324");
676-
assert_eq!(to_string(f, minf64, Minus, (-324, 323), false), format!("0.{:0>323}5", ""));
677-
assert_eq!(to_string(f, minf64, Minus, (-323, 324), false), "5e-324");
775+
{
776+
// f32
777+
assert_eq!(to_string(f, f32::MAX, Minus, (-4, 16), false), "3.4028235e38");
778+
assert_eq!(to_string(f, f32::MAX, Minus, (-39, 38), false), "3.4028235e38");
779+
assert_eq!(to_string(f, f32::MAX, Minus, (-38, 39), false), format!("34028235{:0>31}", ""));
780+
781+
let minf32 = ldexp_f32(1.0, -149);
782+
assert_eq!(to_string(f, minf32, Minus, (-4, 16), false), "1e-45");
783+
assert_eq!(to_string(f, minf32, Minus, (-44, 45), false), "1e-45");
784+
assert_eq!(to_string(f, minf32, Minus, (-45, 44), false), format!("0.{:0>44}1", ""));
785+
}
678786

787+
{
788+
// f64
789+
assert_eq!(to_string(f, f64::MAX, Minus, (-4, 16), false), "1.7976931348623157e308");
790+
assert_eq!(
791+
to_string(f, f64::MAX, Minus, (-308, 309), false),
792+
format!("17976931348623157{:0>292}", "")
793+
);
794+
assert_eq!(to_string(f, f64::MAX, Minus, (-309, 308), false), "1.7976931348623157e308");
795+
796+
let minf64 = ldexp_f64(1.0, -1074);
797+
assert_eq!(to_string(f, minf64, Minus, (-4, 16), false), "5e-324");
798+
assert_eq!(to_string(f, minf64, Minus, (-324, 323), false), format!("0.{:0>323}5", ""));
799+
assert_eq!(to_string(f, minf64, Minus, (-323, 324), false), "5e-324");
800+
}
679801
assert_eq!(to_string(f, 1.1, Minus, (i16::MIN, i16::MAX), false), "1.1");
680802
}
681803

@@ -791,6 +913,26 @@ where
791913
"9.999999999999999547481118258862586856139387236908078193664550781250000e-7"
792914
);
793915

916+
#[cfg(target_has_reliable_f16)]
917+
{
918+
assert_eq!(to_string(f, f16::MAX, Minus, 1, false), "7e4");
919+
assert_eq!(to_string(f, f16::MAX, Minus, 2, false), "6.6e4");
920+
assert_eq!(to_string(f, f16::MAX, Minus, 4, false), "6.550e4");
921+
assert_eq!(to_string(f, f16::MAX, Minus, 5, false), "6.5504e4");
922+
assert_eq!(to_string(f, f16::MAX, Minus, 6, false), "6.55040e4");
923+
assert_eq!(to_string(f, f16::MAX, Minus, 16, false), "6.550400000000000e4");
924+
925+
let minf16 = ldexp_f16(1.0, -24);
926+
assert_eq!(to_string(f, minf16, Minus, 1, false), "6e-8");
927+
assert_eq!(to_string(f, minf16, Minus, 2, false), "6.0e-8");
928+
assert_eq!(to_string(f, minf16, Minus, 4, false), "5.960e-8");
929+
assert_eq!(to_string(f, minf16, Minus, 8, false), "5.9604645e-8");
930+
assert_eq!(to_string(f, minf16, Minus, 16, false), "5.960464477539062e-8");
931+
assert_eq!(to_string(f, minf16, Minus, 17, false), "5.9604644775390625e-8");
932+
assert_eq!(to_string(f, minf16, Minus, 18, false), "5.96046447753906250e-8");
933+
assert_eq!(to_string(f, minf16, Minus, 24, false), "5.96046447753906250000000e-8");
934+
}
935+
794936
assert_eq!(to_string(f, f32::MAX, Minus, 1, false), "3e38");
795937
assert_eq!(to_string(f, f32::MAX, Minus, 2, false), "3.4e38");
796938
assert_eq!(to_string(f, f32::MAX, Minus, 4, false), "3.403e38");
@@ -1069,6 +1211,13 @@ where
10691211
"0.000000999999999999999954748111825886258685613938723690807819366455078125000"
10701212
);
10711213

1214+
#[cfg(target_has_reliable_f16)]
1215+
{
1216+
assert_eq!(to_string(f, f16::MAX, Minus, 0), "65504");
1217+
assert_eq!(to_string(f, f16::MAX, Minus, 1), "65504.0");
1218+
assert_eq!(to_string(f, f16::MAX, Minus, 2), "65504.00");
1219+
}
1220+
10721221
assert_eq!(to_string(f, f32::MAX, Minus, 0), "340282346638528859811704183484516925440");
10731222
assert_eq!(to_string(f, f32::MAX, Minus, 1), "340282346638528859811704183484516925440.0");
10741223
assert_eq!(to_string(f, f32::MAX, Minus, 2), "340282346638528859811704183484516925440.00");
@@ -1078,6 +1227,21 @@ where
10781227
return;
10791228
}
10801229

1230+
#[cfg(target_has_reliable_f16)]
1231+
{
1232+
let minf16 = ldexp_f16(1.0, -24);
1233+
assert_eq!(to_string(f, minf16, Minus, 0), "0");
1234+
assert_eq!(to_string(f, minf16, Minus, 1), "0.0");
1235+
assert_eq!(to_string(f, minf16, Minus, 2), "0.00");
1236+
assert_eq!(to_string(f, minf16, Minus, 4), "0.0000");
1237+
assert_eq!(to_string(f, minf16, Minus, 8), "0.00000006");
1238+
assert_eq!(to_string(f, minf16, Minus, 10), "0.0000000596");
1239+
assert_eq!(to_string(f, minf16, Minus, 15), "0.000000059604645");
1240+
assert_eq!(to_string(f, minf16, Minus, 20), "0.00000005960464477539");
1241+
assert_eq!(to_string(f, minf16, Minus, 24), "0.000000059604644775390625");
1242+
assert_eq!(to_string(f, minf16, Minus, 32), "0.00000005960464477539062500000000");
1243+
}
1244+
10811245
let minf32 = ldexp_f32(1.0, -149);
10821246
assert_eq!(to_string(f, minf32, Minus, 0), "0");
10831247
assert_eq!(to_string(f, minf32, Minus, 1), "0.0");

‎library/coretests/tests/num/flt2dec/random.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ where
7979
(npassed, nignored)
8080
}
8181

82+
#[cfg(target_has_reliable_f16)]
83+
pub fn f16_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
84+
where
85+
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
86+
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
87+
{
88+
let mut rng = crate::test_rng();
89+
let f16_range = Uniform::new(0x0001u16, 0x7c00).unwrap();
90+
iterate("f16_random_equivalence_test", k, n, f, g, |_| {
91+
let x = f16::from_bits(f16_range.sample(&mut rng));
92+
decode_finite(x)
93+
});
94+
}
95+
8296
pub fn f32_random_equivalence_test<F, G>(f: F, g: G, k: usize, n: usize)
8397
where
8498
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
@@ -105,6 +119,24 @@ where
105119
});
106120
}
107121

122+
#[cfg(target_has_reliable_f16)]
123+
pub fn f16_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
124+
where
125+
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
126+
G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
127+
{
128+
// Unlike the other float types, `f16` is small enough that these exhaustive tests
129+
// can run in less than a second so we don't need to ignore it.
130+
131+
// iterate from 0x0001 to 0x7bff, i.e., all finite ranges
132+
let (npassed, nignored) =
133+
iterate("f16_exhaustive_equivalence_test", k, 0x7bff, f, g, |i: usize| {
134+
let x = f16::from_bits(i as u16 + 1);
135+
decode_finite(x)
136+
});
137+
assert_eq!((npassed, nignored), (29735, 2008));
138+
}
139+
108140
pub fn f32_exhaustive_equivalence_test<F, G>(f: F, g: G, k: usize)
109141
where
110142
F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
@@ -133,6 +165,17 @@ fn shortest_random_equivalence_test() {
133165

134166
f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
135167
f32_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
168+
#[cfg(target_has_reliable_f16)]
169+
f16_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, n);
170+
}
171+
172+
#[test]
173+
#[cfg_attr(miri, ignore)] // Miri is to slow
174+
#[cfg(target_has_reliable_f16)]
175+
fn shortest_f16_exhaustive_equivalence_test() {
176+
// see the f32 version
177+
use core::num::flt2dec::strategy::dragon::format_shortest as fallback;
178+
f16_exhaustive_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS);
136179
}
137180

138181
#[test]
@@ -158,6 +201,23 @@ fn shortest_f64_hard_random_equivalence_test() {
158201
f64_random_equivalence_test(format_shortest_opt, fallback, MAX_SIG_DIGITS, 100_000_000);
159202
}
160203

204+
#[test]
205+
#[cfg(target_has_reliable_f16)]
206+
fn exact_f16_random_equivalence_test() {
207+
use core::num::flt2dec::strategy::dragon::format_exact as fallback;
208+
// Miri is too slow
209+
let n = if cfg!(miri) { 3 } else { 1_000 };
210+
211+
for k in 1..21 {
212+
f16_random_equivalence_test(
213+
|d, buf| format_exact_opt(d, buf, i16::MIN),
214+
|d, buf| fallback(d, buf, i16::MIN),
215+
k,
216+
n,
217+
);
218+
}
219+
}
220+
161221
#[test]
162222
fn exact_f32_random_equivalence_test() {
163223
use core::num::flt2dec::strategy::dragon::format_exact as fallback;

‎library/coretests/tests/num/flt2dec/strategy/dragon.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ fn test_mul_pow10() {
1818
fn shortest_sanity_test() {
1919
f64_shortest_sanity_test(format_shortest);
2020
f32_shortest_sanity_test(format_shortest);
21+
#[cfg(target_has_reliable_f16)]
22+
f16_shortest_sanity_test(format_shortest);
2123
more_shortest_sanity_test(format_shortest);
2224
}
2325

@@ -41,6 +43,9 @@ fn exact_sanity_test() {
4143
f64_exact_sanity_test(format_exact);
4244
}
4345
f32_exact_sanity_test(format_exact);
46+
47+
#[cfg(target_has_reliable_f16)]
48+
f16_exact_sanity_test(format_exact);
4449
}
4550

4651
#[test]

‎library/coretests/tests/num/flt2dec/strategy/grisu.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ fn test_max_pow10_no_more_than() {
3838
fn shortest_sanity_test() {
3939
f64_shortest_sanity_test(format_shortest);
4040
f32_shortest_sanity_test(format_shortest);
41+
#[cfg(target_has_reliable_f16)]
42+
f16_shortest_sanity_test(format_shortest);
4143
more_shortest_sanity_test(format_shortest);
4244
}
4345

@@ -50,6 +52,8 @@ fn exact_sanity_test() {
5052
f64_exact_sanity_test(format_exact);
5153
}
5254
f32_exact_sanity_test(format_exact);
55+
#[cfg(target_has_reliable_f16)]
56+
f16_exact_sanity_test(format_exact);
5357
}
5458

5559
#[test]

0 commit comments

Comments
 (0)
Please sign in to comment.