Skip to content

Commit d9a513e

Browse files
committed
Auto merge of rust-lang#127013 - tgross35:f16-format-parse, r=<try>
Add `f16` formatting and parsing Use the same algorithms as for `f32` and `f64` to implement `f16` parsing and printing. try-job: aarch64-gnu
2 parents 414482f + bea36a2 commit d9a513e

File tree

23 files changed

+655
-86
lines changed

23 files changed

+655
-86
lines changed

library/core/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ check-cfg = [
3535
# and to stdarch `core_arch` crate which messes-up with Cargo list
3636
# of declared features, we therefor expect any feature cfg
3737
'cfg(feature, values(any()))',
38+
# Internal features aren't marked known config by default
39+
'cfg(target_has_reliable_f16)',
3840
]

library/core/src/fmt/float.rs

+35
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ macro_rules! impl_general_format {
2020
}
2121
}
2222

23+
impl_general_format! { f16 }
2324
impl_general_format! { f32 f64 }
2425

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

232233
floating! { f32 f64 }
233234

235+
#[cfg(target_has_reliable_f16)]
236+
floating! { f16 }
237+
238+
// FIXME(f16_f128): A fallback is used when the backend+target does not support f16 well, in order
239+
// to avoid ICEs.
240+
241+
#[cfg(not(target_has_reliable_f16))]
234242
#[stable(feature = "rust1", since = "1.0.0")]
235243
impl Debug for f16 {
236244
#[inline]
@@ -239,6 +247,33 @@ impl Debug for f16 {
239247
}
240248
}
241249

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

library/core/src/lib.rs

+1
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

+53-4
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

+17
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,26 @@ 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+
// After the bootstrap bump this should be: `#[cfg(not(target_has_reliable_f16))`
184+
#[cfg(not(target_has_reliable_f16))]
185+
impl FromStr for f16 {
186+
type Err = ParseFloatError;
187+
188+
#[inline]
189+
fn from_str(_src: &str) -> Result<Self, ParseFloatError> {
190+
unimplemented!("requires target_has_reliable_f16")
191+
}
192+
}
193+
177194
/// An error which can be returned when parsing a float.
178195
///
179196
/// This error is used as the error type for the [`FromStr`] implementation

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

+7
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/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,12 @@ test = true
2626
[dev-dependencies]
2727
rand = { version = "0.9.0", default-features = false }
2828
rand_xorshift = { version = "0.4.0", default-features = false }
29+
30+
[lints.rust.unexpected_cfgs]
31+
level = "warn"
32+
check-cfg = [
33+
'cfg(bootstrap)',
34+
# Internal features aren't marked known config by default, we use these to
35+
# gate tests.
36+
'cfg(target_has_reliable_f16)',
37+
]

library/coretests/tests/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#![feature(async_iterator)]
1313
#![feature(bigint_helper_methods)]
1414
#![feature(bstr)]
15+
#![feature(cfg_target_has_reliable_f16_f128)]
1516
#![feature(char_max_len)]
1617
#![feature(clone_to_uninit)]
1718
#![feature(const_eval_select)]
@@ -29,6 +30,7 @@
2930
#![feature(exact_size_is_empty)]
3031
#![feature(extend_one)]
3132
#![feature(extern_types)]
33+
#![feature(f16)]
3234
#![feature(float_minimum_maximum)]
3335
#![feature(flt2dec)]
3436
#![feature(fmt_internals)]

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

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

10+
// FIXME(f16_f128): enable on all targets once possible.
11+
#[test]
12+
#[cfg(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

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

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

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

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

0 commit comments

Comments
 (0)