From 6fc60b8b0493027cc966d4911688f0735bfb329d Mon Sep 17 00:00:00 2001
From: Trevor Gross <tmgross@umich.edu>
Date: Sat, 24 Aug 2024 22:29:39 -0500
Subject: [PATCH 1/3] float: Add `f16` parsing and printing

Use the existing Lemire (decimal -> float) and Dragon / Grisu algorithms
(float -> decimal) to add support for `f16`. This allows updating the
implementation for `Display` to the expected behavior for `Display`
(currently it prints the a hex bitwise representation), matching other
floats, and adds a `FromStr` implementation.

In order to avoid crashes when compiling with Cranelift or on targets
where f16 is not well supported, a fallback is used if
`cfg(target_has_reliable_f16)` is not true.
---
 library/core/src/fmt/float.rs           | 36 ++++++++++++++++
 library/core/src/lib.rs                 |  1 +
 library/core/src/num/dec2flt/float.rs   | 57 +++++++++++++++++++++++--
 library/core/src/num/dec2flt/mod.rs     | 16 +++++++
 library/core/src/num/flt2dec/decoder.rs |  7 +++
 5 files changed, 113 insertions(+), 4 deletions(-)

diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs
index 870ad9df4fd33..556db239f2499 100644
--- a/library/core/src/fmt/float.rs
+++ b/library/core/src/fmt/float.rs
@@ -20,6 +20,8 @@ macro_rules! impl_general_format {
     }
 }
 
+#[cfg(target_has_reliable_f16)]
+impl_general_format! { f16 }
 impl_general_format! { f32 f64 }
 
 // Don't inline this so callers don't use the stack space this function
@@ -231,6 +233,13 @@ macro_rules! floating {
 
 floating! { f32 f64 }
 
+#[cfg(target_has_reliable_f16)]
+floating! { f16 }
+
+// FIXME(f16_f128): A fallback is used when the backend+target does not support f16 well, in order
+// to avoid ICEs.
+
+#[cfg(not(target_has_reliable_f16))]
 #[stable(feature = "rust1", since = "1.0.0")]
 impl Debug for f16 {
     #[inline]
@@ -239,6 +248,33 @@ impl Debug for f16 {
     }
 }
 
+#[cfg(not(target_has_reliable_f16))]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl Display for f16 {
+    #[inline]
+    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
+        Debug::fmt(self, fmt)
+    }
+}
+
+#[cfg(not(target_has_reliable_f16))]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl LowerExp for f16 {
+    #[inline]
+    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
+        Debug::fmt(self, fmt)
+    }
+}
+
+#[cfg(not(target_has_reliable_f16))]
+#[stable(feature = "rust1", since = "1.0.0")]
+impl UpperExp for f16 {
+    #[inline]
+    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result {
+        Debug::fmt(self, fmt)
+    }
+}
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl Debug for f128 {
     #[inline]
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 64a7ec8906b6b..54555f8beec63 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -101,6 +101,7 @@
 #![feature(bstr)]
 #![feature(bstr_internals)]
 #![feature(cfg_match)]
+#![feature(cfg_target_has_reliable_f16_f128)]
 #![feature(const_carrying_mul_add)]
 #![feature(const_eval_select)]
 #![feature(core_intrinsics)]
diff --git a/library/core/src/num/dec2flt/float.rs b/library/core/src/num/dec2flt/float.rs
index b8a28a6756917..5bf0faf0bc910 100644
--- a/library/core/src/num/dec2flt/float.rs
+++ b/library/core/src/num/dec2flt/float.rs
@@ -45,7 +45,7 @@ macro_rules! int {
     }
 }
 
-int!(u32, u64);
+int!(u16, u32, u64);
 
 /// A helper trait to avoid duplicating basically all the conversion code for IEEE floats.
 ///
@@ -189,9 +189,14 @@ pub trait RawFloat:
 
     /// Returns the mantissa, exponent and sign as integers.
     ///
-    /// That is, this returns `(m, p, s)` such that `s * m * 2^p` represents the original float.
-    /// For 0, the exponent will be `-(EXP_BIAS + SIG_BITS`, which is the
-    /// minimum subnormal power.
+    /// This returns `(m, p, s)` such that `s * m * 2^p` represents the original float. For 0, the
+    /// exponent will be `-(EXP_BIAS + SIG_BITS)`, which is the minimum subnormal power. For
+    /// infinity or NaN, the exponent will be `EXP_SAT - EXP_BIAS - SIG_BITS`.
+    ///
+    /// If subnormal, the mantissa will be shifted one bit to the left. Otherwise, it is returned
+    /// with the explicit bit set but otherwise unshifted
+    ///
+    /// `s` is only ever +/-1.
     fn integer_decode(self) -> (u64, i16, i8) {
         let bits = self.to_bits();
         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 {
     res as i64
 }
 
+#[cfg(target_has_reliable_f16)]
+impl RawFloat for f16 {
+    type Int = u16;
+
+    const INFINITY: Self = Self::INFINITY;
+    const NEG_INFINITY: Self = Self::NEG_INFINITY;
+    const NAN: Self = Self::NAN;
+    const NEG_NAN: Self = -Self::NAN;
+
+    const BITS: u32 = 16;
+    const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
+    const EXP_MASK: Self::Int = Self::EXP_MASK;
+    const SIG_MASK: Self::Int = Self::MAN_MASK;
+
+    const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -22;
+    const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 5;
+    const SMALLEST_POWER_OF_TEN: i32 = -27;
+
+    #[inline]
+    fn from_u64(v: u64) -> Self {
+        debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH);
+        v as _
+    }
+
+    #[inline]
+    fn from_u64_bits(v: u64) -> Self {
+        Self::from_bits((v & 0xFFFF) as u16)
+    }
+
+    fn pow10_fast_path(exponent: usize) -> Self {
+        #[allow(clippy::use_self)]
+        const TABLE: [f16; 8] = [1e0, 1e1, 1e2, 1e3, 1e4, 0.0, 0.0, 0.];
+        TABLE[exponent & 7]
+    }
+
+    fn to_bits(self) -> Self::Int {
+        self.to_bits()
+    }
+
+    fn classify(self) -> FpCategory {
+        self.classify()
+    }
+}
+
 impl RawFloat for f32 {
     type Int = u32;
 
diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs
index d1a0e1db31314..abad7acb1046a 100644
--- a/library/core/src/num/dec2flt/mod.rs
+++ b/library/core/src/num/dec2flt/mod.rs
@@ -171,9 +171,25 @@ macro_rules! from_str_float_impl {
         }
     };
 }
+
+#[cfg(target_has_reliable_f16)]
+from_str_float_impl!(f16);
 from_str_float_impl!(f32);
 from_str_float_impl!(f64);
 
+// FIXME(f16_f128): A fallback is used when the backend+target does not support f16 well, in order
+// to avoid ICEs.
+
+#[cfg(not(target_has_reliable_f16))]
+impl FromStr for f16 {
+    type Err = ParseFloatError;
+
+    #[inline]
+    fn from_str(_src: &str) -> Result<Self, ParseFloatError> {
+        unimplemented!("requires target_has_reliable_f16")
+    }
+}
+
 /// An error which can be returned when parsing a float.
 ///
 /// This error is used as the error type for the [`FromStr`] implementation
diff --git a/library/core/src/num/flt2dec/decoder.rs b/library/core/src/num/flt2dec/decoder.rs
index 40b3aae24a536..bd6e2cdbafec8 100644
--- a/library/core/src/num/flt2dec/decoder.rs
+++ b/library/core/src/num/flt2dec/decoder.rs
@@ -45,6 +45,13 @@ pub trait DecodableFloat: RawFloat + Copy {
     fn min_pos_norm_value() -> Self;
 }
 
+#[cfg(target_has_reliable_f16)]
+impl DecodableFloat for f16 {
+    fn min_pos_norm_value() -> Self {
+        f16::MIN_POSITIVE
+    }
+}
+
 impl DecodableFloat for f32 {
     fn min_pos_norm_value() -> Self {
         f32::MIN_POSITIVE

From 977d8418696438c8cc5f21082d59cdc6d09d94bf Mon Sep 17 00:00:00 2001
From: Trevor Gross <tmgross@umich.edu>
Date: Sat, 8 Mar 2025 03:39:04 +0000
Subject: [PATCH 2/3] 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>
---
 .../coretests/tests/num/dec2flt/decimal.rs    |  14 ++
 library/coretests/tests/num/dec2flt/float.rs  |  40 +++
 library/coretests/tests/num/dec2flt/lemire.rs | 133 +++++++---
 library/coretests/tests/num/dec2flt/mod.rs    |  65 ++++-
 library/coretests/tests/num/dec2flt/parse.rs  |  23 +-
 library/coretests/tests/num/flt2dec/mod.rs    | 234 +++++++++++++++---
 library/coretests/tests/num/flt2dec/random.rs |  60 +++++
 .../tests/num/flt2dec/strategy/dragon.rs      |   5 +
 .../tests/num/flt2dec/strategy/grisu.rs       |   4 +
 9 files changed, 501 insertions(+), 77 deletions(-)

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

From 250869e909e643c90582337406e943009569fc6e Mon Sep 17 00:00:00 2001
From: Trevor Gross <tmgross@umich.edu>
Date: Sun, 25 Aug 2024 01:30:17 -0500
Subject: [PATCH 3/3] float: Add `f16` to `test-float-parse`

This requires a fix to the subnormal test to cap the maximum allowed
value within the maximum mantissa.
---
 src/bootstrap/src/core/build_steps/test.rs   | 4 +++-
 src/bootstrap/src/core/build_steps/tool.rs   | 6 +++++-
 src/etc/test-float-parse/Cargo.toml          | 7 +++++++
 src/etc/test-float-parse/src/gen_/subnorm.rs | 9 +++++++--
 src/etc/test-float-parse/src/lib.rs          | 7 +++++++
 src/etc/test-float-parse/src/traits.rs       | 5 ++++-
 6 files changed, 33 insertions(+), 5 deletions(-)

diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index b2dc509ddca0e..27791825aa0f6 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -3554,7 +3554,7 @@ impl Step for TestFloatParse {
         builder.ensure(tool::TestFloatParse { host: self.host });
 
         // Run any unit tests in the crate
-        let cargo_test = tool::prepare_tool_cargo(
+        let mut cargo_test = tool::prepare_tool_cargo(
             builder,
             compiler,
             Mode::ToolStd,
@@ -3564,6 +3564,7 @@ impl Step for TestFloatParse {
             SourceType::InTree,
             &[],
         );
+        cargo_test.allow_features(tool::TestFloatParse::ALLOW_FEATURES);
 
         run_cargo_test(cargo_test, &[], &[], crate_name, bootstrap_host, builder);
 
@@ -3578,6 +3579,7 @@ impl Step for TestFloatParse {
             SourceType::InTree,
             &[],
         );
+        cargo_run.allow_features(tool::TestFloatParse::ALLOW_FEATURES);
 
         if !matches!(env::var("FLOAT_PARSE_TESTS_NO_SKIP_HUGE").as_deref(), Ok("1") | Ok("true")) {
             cargo_run.args(["--", "--skip-huge"]);
diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs
index ac568eab2e8a5..678aa9b01e4ad 100644
--- a/src/bootstrap/src/core/build_steps/tool.rs
+++ b/src/bootstrap/src/core/build_steps/tool.rs
@@ -1259,6 +1259,10 @@ pub struct TestFloatParse {
     pub host: TargetSelection,
 }
 
+impl TestFloatParse {
+    pub const ALLOW_FEATURES: &'static str = "f16,cfg_target_has_reliable_f16_f128";
+}
+
 impl Step for TestFloatParse {
     type Output = ToolBuildResult;
     const ONLY_HOSTS: bool = true;
@@ -1280,7 +1284,7 @@ impl Step for TestFloatParse {
             path: "src/etc/test-float-parse",
             source_type: SourceType::InTree,
             extra_features: Vec::new(),
-            allow_features: "",
+            allow_features: Self::ALLOW_FEATURES,
             cargo_args: Vec::new(),
             artifact_kind: ToolArtifactKind::Binary,
         })
diff --git a/src/etc/test-float-parse/Cargo.toml b/src/etc/test-float-parse/Cargo.toml
index 8a9c5322ef7bd..e407e322f9e19 100644
--- a/src/etc/test-float-parse/Cargo.toml
+++ b/src/etc/test-float-parse/Cargo.toml
@@ -13,3 +13,10 @@ rayon = "1"
 
 [lib]
 name = "test_float_parse"
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = [
+    # Internal features aren't marked known config by default
+    'cfg(target_has_reliable_f16)',
+]
diff --git a/src/etc/test-float-parse/src/gen_/subnorm.rs b/src/etc/test-float-parse/src/gen_/subnorm.rs
index 4fe3b90a3ddf4..654f324b9b011 100644
--- a/src/etc/test-float-parse/src/gen_/subnorm.rs
+++ b/src/etc/test-float-parse/src/gen_/subnorm.rs
@@ -1,4 +1,3 @@
-use std::cmp::min;
 use std::fmt::Write;
 use std::ops::RangeInclusive;
 
@@ -83,7 +82,13 @@ where
     }
 
     fn new() -> Self {
-        Self { iter: F::Int::ZERO..=min(F::Int::ONE << 22, F::MAN_BITS.try_into().unwrap()) }
+        let upper_lim = if F::MAN_BITS >= 22 {
+            F::Int::ONE << 22
+        } else {
+            (F::Int::ONE << F::MAN_BITS) - F::Int::ONE
+        };
+
+        Self { iter: F::Int::ZERO..=upper_lim }
     }
 
     fn write_string(s: &mut String, ctx: Self::WriteCtx) {
diff --git a/src/etc/test-float-parse/src/lib.rs b/src/etc/test-float-parse/src/lib.rs
index 3c3ef5802b6aa..0bd4878f9a626 100644
--- a/src/etc/test-float-parse/src/lib.rs
+++ b/src/etc/test-float-parse/src/lib.rs
@@ -1,3 +1,7 @@
+#![feature(f16)]
+#![feature(cfg_target_has_reliable_f16_f128)]
+#![expect(internal_features)] // reliable_f16_f128
+
 mod traits;
 mod ui;
 mod validate;
@@ -114,6 +118,9 @@ pub fn register_tests(cfg: &Config) -> Vec<TestInfo> {
     let mut tests = Vec::new();
 
     // Register normal generators for all floats.
+
+    #[cfg(target_has_reliable_f16)]
+    register_float::<f16>(&mut tests, cfg);
     register_float::<f32>(&mut tests, cfg);
     register_float::<f64>(&mut tests, cfg);
 
diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs
index 57e702b7d0913..65a8721bfa5cd 100644
--- a/src/etc/test-float-parse/src/traits.rs
+++ b/src/etc/test-float-parse/src/traits.rs
@@ -98,7 +98,7 @@ macro_rules! impl_int {
     }
 }
 
-impl_int!(u32, i32; u64, i64);
+impl_int!(u16, i16; u32, i32; u64, i64);
 
 /// Floating point types.
 pub trait Float:
@@ -170,6 +170,9 @@ macro_rules! impl_float {
 
 impl_float!(f32, u32; f64, u64);
 
+#[cfg(target_has_reliable_f16)]
+impl_float!(f16, u16);
+
 /// A test generator. Should provide an iterator that produces unique patterns to parse.
 ///
 /// The iterator needs to provide a `WriteCtx` (could be anything), which is then used to