Skip to content

Commit f55bff2

Browse files
tgross35speedy-lex
andcommitted
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 d12f15b commit f55bff2

File tree

10 files changed

+517
-76
lines changed

10 files changed

+517
-76
lines changed

library/coretests/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![feature(exact_size_is_empty)]
3131
#![feature(extend_one)]
3232
#![feature(extern_types)]
33+
#![feature(f16)]
3334
#![feature(float_minimum_maximum)]
3435
#![feature(flt2dec)]
3536
#![feature(fmt_internals)]

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

+14
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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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

+39
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
use core::num::dec2flt::float::RawFloat;
22

3+
// FIXME(f16_f128): enable on all targets once possible.
4+
#[test]
5+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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+
assert_eq!(2f16.powf(14.0).integer_decode(), (1 << 10, 4, 1));
10+
assert_eq!(0f16.integer_decode(), (0, -25, 1));
11+
assert_eq!((-0f16).integer_decode(), (0, -25, -1));
12+
assert_eq!(f16::INFINITY.integer_decode(), (1 << 10, 6, 1));
13+
assert_eq!(f16::NEG_INFINITY.integer_decode(), (1 << 10, 6, -1));
14+
15+
// Ignore the "sign" (quiet / signalling flag) of NAN.
16+
// It can vary between runtime operations and LLVM folding.
17+
let (nan_m, nan_p, _nan_s) = f16::NAN.integer_decode();
18+
assert_eq!((nan_m, nan_p), (1536, 6));
19+
}
20+
321
#[test]
422
fn test_f32_integer_decode() {
523
assert_eq!(3.14159265359f32.integer_decode(), (13176795, -22, 1));
@@ -34,6 +52,27 @@ fn test_f64_integer_decode() {
3452

3553
/* Sanity checks of computed magic numbers */
3654

55+
// FIXME(f16_f128): enable on all targets once possible.
56+
#[test]
57+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
58+
fn test_f16_consts() {
59+
assert_eq!(<f16 as RawFloat>::INFINITY, f16::INFINITY);
60+
assert_eq!(<f16 as RawFloat>::NEG_INFINITY, -f16::INFINITY);
61+
assert_eq!(<f16 as RawFloat>::NAN.to_bits(), f16::NAN.to_bits());
62+
assert_eq!(<f16 as RawFloat>::NEG_NAN.to_bits(), (-f16::NAN).to_bits());
63+
assert_eq!(<f16 as RawFloat>::SIG_BITS, 10);
64+
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -22);
65+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 5);
66+
assert_eq!(<f16 as RawFloat>::MIN_EXPONENT_FAST_PATH, -4);
67+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_FAST_PATH, 4);
68+
assert_eq!(<f16 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 7);
69+
assert_eq!(<f16 as RawFloat>::EXP_MIN, -14);
70+
assert_eq!(<f16 as RawFloat>::EXP_SAT, 0x1f);
71+
assert_eq!(<f16 as RawFloat>::SMALLEST_POWER_OF_TEN, -27);
72+
assert_eq!(<f16 as RawFloat>::LARGEST_POWER_OF_TEN, 4);
73+
assert_eq!(<f16 as RawFloat>::MAX_MANTISSA_FAST_PATH, 2048);
74+
}
75+
3776
#[test]
3877
fn test_f32_consts() {
3978
assert_eq!(<f32 as RawFloat>::INFINITY, f32::INFINITY);
+102-31
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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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

+53-9
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@ 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+
let x16: f16 = $x;
1415
let x32: f32 = $x;
1516
let x64: f64 = $x;
1617
let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
18+
1719
for input in inputs {
18-
assert_eq!(input.parse(), Ok(x64));
19-
assert_eq!(input.parse(), Ok(x32));
20+
assert_eq!(input.parse(), Ok(x64), "failed f64 {input}");
21+
assert_eq!(input.parse(), Ok(x32), "failed f32 {input}");
22+
assert_eq!(input.parse(), Ok(x16), "failed f16 {input}");
23+
2024
let neg_input = format!("-{input}");
21-
assert_eq!(neg_input.parse(), Ok(-x64));
22-
assert_eq!(neg_input.parse(), Ok(-x32));
25+
assert_eq!(neg_input.parse(), Ok(-x64), "failed f64 {neg_input}");
26+
assert_eq!(neg_input.parse(), Ok(-x32), "failed f32 {neg_input}");
27+
assert_eq!(neg_input.parse(), Ok(-x16), "failed f16 {neg_input}");
2328
}
2429
}};
2530
}
@@ -84,48 +89,87 @@ fn fast_path_correct() {
8489
test_literal!(1.448997445238699);
8590
}
8691

92+
// FIXME(f16_f128): remove gates once tests work on all targets
93+
8794
#[test]
8895
fn lonely_dot() {
96+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
97+
assert!(".".parse::<f16>().is_err());
8998
assert!(".".parse::<f32>().is_err());
9099
assert!(".".parse::<f64>().is_err());
91100
}
92101

93102
#[test]
94103
fn exponentiated_dot() {
104+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
105+
assert!(".e0".parse::<f16>().is_err());
95106
assert!(".e0".parse::<f32>().is_err());
96107
assert!(".e0".parse::<f64>().is_err());
97108
}
98109

99110
#[test]
100111
fn lonely_sign() {
101-
assert!("+".parse::<f32>().is_err());
102-
assert!("-".parse::<f64>().is_err());
112+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
113+
assert!("+".parse::<f16>().is_err());
114+
assert!("-".parse::<f32>().is_err());
115+
assert!("+".parse::<f64>().is_err());
103116
}
104117

105118
#[test]
106119
fn whitespace() {
120+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
121+
assert!("1.0 ".parse::<f16>().is_err());
107122
assert!(" 1.0".parse::<f32>().is_err());
108123
assert!("1.0 ".parse::<f64>().is_err());
109124
}
110125

111126
#[test]
112127
fn nan() {
128+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
129+
{
130+
assert!("NaN".parse::<f16>().unwrap().is_nan());
131+
assert!("-NaN".parse::<f16>().unwrap().is_nan());
132+
}
133+
113134
assert!("NaN".parse::<f32>().unwrap().is_nan());
135+
assert!("-NaN".parse::<f32>().unwrap().is_nan());
136+
114137
assert!("NaN".parse::<f64>().unwrap().is_nan());
138+
assert!("-NaN".parse::<f64>().unwrap().is_nan());
115139
}
116140

117141
#[test]
118142
fn inf() {
119-
assert_eq!("inf".parse(), Ok(f64::INFINITY));
120-
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
143+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
144+
{
145+
assert_eq!("inf".parse(), Ok(f16::INFINITY));
146+
assert_eq!("-inf".parse(), Ok(f16::NEG_INFINITY));
147+
}
148+
121149
assert_eq!("inf".parse(), Ok(f32::INFINITY));
122150
assert_eq!("-inf".parse(), Ok(f32::NEG_INFINITY));
151+
152+
assert_eq!("inf".parse(), Ok(f64::INFINITY));
153+
assert_eq!("-inf".parse(), Ok(f64::NEG_INFINITY));
123154
}
124155

125156
#[test]
126157
fn massive_exponent() {
158+
#[cfg(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
159+
{
160+
let max = i16::MAX;
161+
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
162+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f16));
163+
assert_eq!(format!("1e{max}000").parse(), Ok(f16::INFINITY));
164+
}
165+
166+
let max = i32::MAX;
167+
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
168+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f32));
169+
assert_eq!(format!("1e{max}000").parse(), Ok(f32::INFINITY));
170+
127171
let max = i64::MAX;
128172
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
129-
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0));
173+
assert_eq!(format!("1e-{max}000").parse(), Ok(0.0f64));
130174
assert_eq!(format!("1e{max}000").parse(), Ok(f64::INFINITY));
131175
}

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

+19-2
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,39 @@ 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+
assert_eq!(dec2flt::<f16>(s), Err(pfe_invalid()));
14+
assert_eq!(dec2flt::<f32>(s), Err(pfe_invalid()));
1315
assert_eq!(dec2flt::<f64>(s), Err(pfe_invalid()));
1416
}
1517
}
1618

1719
#[test]
1820
fn invalid_chars() {
1921
let invalid = "r,?<j";
20-
let error = Err(pfe_invalid());
2122
let valid_strings = &["123", "666.", ".1", "5e1", "7e-3", "0.0e+1"];
23+
2224
for c in invalid.chars() {
2325
for s in valid_strings {
2426
for i in 0..s.len() {
2527
let mut input = String::new();
2628
input.push_str(s);
2729
input.insert(i, c);
28-
assert!(dec2flt::<f64>(&input) == error, "did not reject invalid {:?}", input);
30+
31+
assert_eq!(
32+
dec2flt::<f16>(&input),
33+
Err(pfe_invalid()),
34+
"f16 did not reject invalid {input:?}",
35+
);
36+
assert_eq!(
37+
dec2flt::<f32>(&input),
38+
Err(pfe_invalid()),
39+
"f32 did not reject invalid {input:?}",
40+
);
41+
assert_eq!(
42+
dec2flt::<f64>(&input),
43+
Err(pfe_invalid()),
44+
"f64 did not reject invalid {input:?}",
45+
);
2946
}
3047
}
3148
}

0 commit comments

Comments
 (0)