Skip to content

Commit c645a0b

Browse files
committed
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.
1 parent 414482f commit c645a0b

File tree

6 files changed

+116
-4
lines changed

6 files changed

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

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

+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

0 commit comments

Comments
 (0)