Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4d051fb

Browse files
committedMay 18, 2025·
Auto merge of #127013 - tgross35:f16-format-parse, r=Mark-Simulacrum
Add `f16` formatting and parsing Use the same algorithms as for `f32` and `f64` to implement `f16` parsing and printing. try-job: x86_64-gnu-aux
2 parents 4455c89 + 250869e commit 4d051fb

File tree

20 files changed

+647
-86
lines changed

20 files changed

+647
-86
lines changed
 

‎library/core/src/fmt/float.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ macro_rules! impl_general_format {
2020
}
2121
}
2222

23+
#[cfg(target_has_reliable_f16)]
24+
impl_general_format! { f16 }
2325
impl_general_format! { f32 f64 }
2426

2527
// Don't inline this so callers don't use the stack space this function
@@ -231,6 +233,13 @@ macro_rules! floating {
231233

232234
floating! { f32 f64 }
233235

236+
#[cfg(target_has_reliable_f16)]
237+
floating! { f16 }
238+
239+
// FIXME(f16_f128): A fallback is used when the backend+target does not support f16 well, in order
240+
// to avoid ICEs.
241+
242+
#[cfg(not(target_has_reliable_f16))]
234243
#[stable(feature = "rust1", since = "1.0.0")]
235244
impl Debug for f16 {
236245
#[inline]
@@ -239,6 +248,33 @@ impl Debug for f16 {
239248
}
240249
}
241250

251+
#[cfg(not(target_has_reliable_f16))]
252+
#[stable(feature = "rust1", since = "1.0.0")]
253+
impl Display for f16 {
254+
#[inline]
255+
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
256+
Debug::fmt(self, fmt)
257+
}
258+
}
259+
260+
#[cfg(not(target_has_reliable_f16))]
261+
#[stable(feature = "rust1", since = "1.0.0")]
262+
impl LowerExp for f16 {
263+
#[inline]
264+
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
265+
Debug::fmt(self, fmt)
266+
}
267+
}
268+
269+
#[cfg(not(target_has_reliable_f16))]
270+
#[stable(feature = "rust1", since = "1.0.0")]
271+
impl UpperExp for f16 {
272+
#[inline]
273+
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
274+
Debug::fmt(self, fmt)
275+
}
276+
}
277+
242278
#[stable(feature = "rust1", since = "1.0.0")]
243279
impl Debug for f128 {
244280
#[inline]

‎library/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
#![feature(bstr)]
102102
#![feature(bstr_internals)]
103103
#![feature(cfg_match)]
104+
#![feature(cfg_target_has_reliable_f16_f128)]
104105
#![feature(const_carrying_mul_add)]
105106
#![feature(const_eval_select)]
106107
#![feature(core_intrinsics)]

‎library/core/src/num/dec2flt/float.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ macro_rules! int {
4545
}
4646
}
4747

48-
int!(u32, u64);
48+
int!(u16, u32, u64);
4949

5050
/// A helper trait to avoid duplicating basically all the conversion code for IEEE floats.
5151
///
@@ -189,9 +189,14 @@ pub trait RawFloat:
189189

190190
/// Returns the mantissa, exponent and sign as integers.
191191
///
192-
/// That is, this returns `(m, p, s)` such that `s * m * 2^p` represents the original float.
193-
/// For 0, the exponent will be `-(EXP_BIAS + SIG_BITS`, which is the
194-
/// minimum subnormal power.
192+
/// This returns `(m, p, s)` such that `s * m * 2^p` represents the original float. For 0, the
193+
/// exponent will be `-(EXP_BIAS + SIG_BITS)`, which is the minimum subnormal power. For
194+
/// infinity or NaN, the exponent will be `EXP_SAT - EXP_BIAS - SIG_BITS`.
195+
///
196+
/// If subnormal, the mantissa will be shifted one bit to the left. Otherwise, it is returned
197+
/// with the explicit bit set but otherwise unshifted
198+
///
199+
/// `s` is only ever +/-1.
195200
fn integer_decode(self) -> (u64, i16, i8) {
196201
let bits = self.to_bits();
197202
let sign: i8 = if bits >> (Self::BITS - 1) == Self::Int::ZERO { 1 } else { -1 };
@@ -213,6 +218,50 @@ const fn pow2_to_pow10(a: i64) -> i64 {
213218
res as i64
214219
}
215220

221+
#[cfg(target_has_reliable_f16)]
222+
impl RawFloat for f16 {
223+
type Int = u16;
224+
225+
const INFINITY: Self = Self::INFINITY;
226+
const NEG_INFINITY: Self = Self::NEG_INFINITY;
227+
const NAN: Self = Self::NAN;
228+
const NEG_NAN: Self = -Self::NAN;
229+
230+
const BITS: u32 = 16;
231+
const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
232+
const EXP_MASK: Self::Int = Self::EXP_MASK;
233+
const SIG_MASK: Self::Int = Self::MAN_MASK;
234+
235+
const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -22;
236+
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 5;
237+
const SMALLEST_POWER_OF_TEN: i32 = -27;
238+
239+
#[inline]
240+
fn from_u64(v: u64) -> Self {
241+
debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH);
242+
v as _
243+
}
244+
245+
#[inline]
246+
fn from_u64_bits(v: u64) -> Self {
247+
Self::from_bits((v & 0xFFFF) as u16)
248+
}
249+
250+
fn pow10_fast_path(exponent: usize) -> Self {
251+
#[allow(clippy::use_self)]
252+
const TABLE: [f16; 8] = [1e0, 1e1, 1e2, 1e3, 1e4, 0.0, 0.0, 0.];
253+
TABLE[exponent & 7]
254+
}
255+
256+
fn to_bits(self) -> Self::Int {
257+
self.to_bits()
258+
}
259+
260+
fn classify(self) -> FpCategory {
261+
self.classify()
262+
}
263+
}
264+
216265
impl RawFloat for f32 {
217266
type Int = u32;
218267

‎library/core/src/num/dec2flt/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,25 @@ macro_rules! from_str_float_impl {
171171
}
172172
};
173173
}
174+
175+
#[cfg(target_has_reliable_f16)]
176+
from_str_float_impl!(f16);
174177
from_str_float_impl!(f32);
175178
from_str_float_impl!(f64);
176179

180+
// FIXME(f16_f128): A fallback is used when the backend+target does not support f16 well, in order
181+
// to avoid ICEs.
182+
183+
#[cfg(not(target_has_reliable_f16))]
184+
impl FromStr for f16 {
185+
type Err = ParseFloatError;
186+
187+
#[inline]
188+
fn from_str(_src: &str) -> Result<Self, ParseFloatError> {
189+
unimplemented!("requires target_has_reliable_f16")
190+
}
191+
}
192+
177193
/// An error which can be returned when parsing a float.
178194
///
179195
/// This error is used as the error type for the [`FromStr`] implementation

‎library/core/src/num/flt2dec/decoder.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ pub trait DecodableFloat: RawFloat + Copy {
4545
fn min_pos_norm_value() -> Self;
4646
}
4747

48+
#[cfg(target_has_reliable_f16)]
49+
impl DecodableFloat for f16 {
50+
fn min_pos_norm_value() -> Self {
51+
f16::MIN_POSITIVE
52+
}
53+
}
54+
4855
impl DecodableFloat for f32 {
4956
fn min_pos_norm_value() -> Self {
5057
f32::MIN_POSITIVE

‎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]

‎src/bootstrap/src/core/build_steps/test.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3554,7 +3554,7 @@ impl Step for TestFloatParse {
35543554
builder.ensure(tool::TestFloatParse { host: self.host });
35553555

35563556
// Run any unit tests in the crate
3557-
let cargo_test = tool::prepare_tool_cargo(
3557+
let mut cargo_test = tool::prepare_tool_cargo(
35583558
builder,
35593559
compiler,
35603560
Mode::ToolStd,
@@ -3564,6 +3564,7 @@ impl Step for TestFloatParse {
35643564
SourceType::InTree,
35653565
&[],
35663566
);
3567+
cargo_test.allow_features(tool::TestFloatParse::ALLOW_FEATURES);
35673568

35683569
run_cargo_test(cargo_test, &[], &[], crate_name, bootstrap_host, builder);
35693570

@@ -3578,6 +3579,7 @@ impl Step for TestFloatParse {
35783579
SourceType::InTree,
35793580
&[],
35803581
);
3582+
cargo_run.allow_features(tool::TestFloatParse::ALLOW_FEATURES);
35813583

35823584
if !matches!(env::var("FLOAT_PARSE_TESTS_NO_SKIP_HUGE").as_deref(), Ok("1") | Ok("true")) {
35833585
cargo_run.args(["--", "--skip-huge"]);

‎src/bootstrap/src/core/build_steps/tool.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,10 @@ pub struct TestFloatParse {
12591259
pub host: TargetSelection,
12601260
}
12611261

1262+
impl TestFloatParse {
1263+
pub const ALLOW_FEATURES: &'static str = "f16,cfg_target_has_reliable_f16_f128";
1264+
}
1265+
12621266
impl Step for TestFloatParse {
12631267
type Output = ToolBuildResult;
12641268
const ONLY_HOSTS: bool = true;
@@ -1280,7 +1284,7 @@ impl Step for TestFloatParse {
12801284
path: "src/etc/test-float-parse",
12811285
source_type: SourceType::InTree,
12821286
extra_features: Vec::new(),
1283-
allow_features: "",
1287+
allow_features: Self::ALLOW_FEATURES,
12841288
cargo_args: Vec::new(),
12851289
artifact_kind: ToolArtifactKind::Binary,
12861290
})

‎src/etc/test-float-parse/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ rayon = "1"
1313

1414
[lib]
1515
name = "test_float_parse"
16+
17+
[lints.rust.unexpected_cfgs]
18+
level = "warn"
19+
check-cfg = [
20+
# Internal features aren't marked known config by default
21+
'cfg(target_has_reliable_f16)',
22+
]

‎src/etc/test-float-parse/src/gen_/subnorm.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::cmp::min;
21
use std::fmt::Write;
32
use std::ops::RangeInclusive;
43

@@ -83,7 +82,13 @@ where
8382
}
8483

8584
fn new() -> Self {
86-
Self { iter: F::Int::ZERO..=min(F::Int::ONE << 22, F::MAN_BITS.try_into().unwrap()) }
85+
let upper_lim = if F::MAN_BITS >= 22 {
86+
F::Int::ONE << 22
87+
} else {
88+
(F::Int::ONE << F::MAN_BITS) - F::Int::ONE
89+
};
90+
91+
Self { iter: F::Int::ZERO..=upper_lim }
8792
}
8893

8994
fn write_string(s: &mut String, ctx: Self::WriteCtx) {

‎src/etc/test-float-parse/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#![feature(f16)]
2+
#![feature(cfg_target_has_reliable_f16_f128)]
3+
#![expect(internal_features)] // reliable_f16_f128
4+
15
mod traits;
26
mod ui;
37
mod validate;
@@ -114,6 +118,9 @@ pub fn register_tests(cfg: &Config) -> Vec<TestInfo> {
114118
let mut tests = Vec::new();
115119

116120
// Register normal generators for all floats.
121+
122+
#[cfg(target_has_reliable_f16)]
123+
register_float::<f16>(&mut tests, cfg);
117124
register_float::<f32>(&mut tests, cfg);
118125
register_float::<f64>(&mut tests, cfg);
119126

‎src/etc/test-float-parse/src/traits.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ macro_rules! impl_int {
9898
}
9999
}
100100

101-
impl_int!(u32, i32; u64, i64);
101+
impl_int!(u16, i16; u32, i32; u64, i64);
102102

103103
/// Floating point types.
104104
pub trait Float:
@@ -170,6 +170,9 @@ macro_rules! impl_float {
170170

171171
impl_float!(f32, u32; f64, u64);
172172

173+
#[cfg(target_has_reliable_f16)]
174+
impl_float!(f16, u16);
175+
173176
/// A test generator. Should provide an iterator that produces unique patterns to parse.
174177
///
175178
/// The iterator needs to provide a `WriteCtx` (could be anything), which is then used to

0 commit comments

Comments
 (0)
Please sign in to comment.