Skip to content

Add f16 formatting and parsing #127013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ index 1e336bf..35e6f54 100644
+++ b/coretests/tests/lib.rs
@@ -2,5 +2,4 @@
// tidy-alphabetical-start
#![cfg_attr(not(bootstrap), feature(cfg_target_has_reliable_f16_f128))]
-#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
#![cfg_attr(test, feature(cfg_match))]
#![feature(alloc_layout_extra)]
#![feature(array_chunks)]
diff --git a/coretests/tests/atomic.rs b/coretests/tests/atomic.rs
index b735957..ea728b6 100644
--- a/coretests/tests/atomic.rs
Expand Down
2 changes: 2 additions & 0 deletions library/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ check-cfg = [
# and to stdarch `core_arch` crate which messes-up with Cargo list
# of declared features, we therefor expect any feature cfg
'cfg(feature, values(any()))',
# Internal features aren't marked known config by default
'cfg(target_has_reliable_f16)',
]
49 changes: 49 additions & 0 deletions library/core/src/fmt/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ macro_rules! impl_general_format {
}
}

impl_general_format! { f16 }
impl_general_format! { f32 f64 }

// Don't inline this so callers don't use the stack space this function
Expand Down Expand Up @@ -231,6 +232,7 @@ macro_rules! floating {

floating! { f32 f64 }

#[cfg(bootstrap)]
#[stable(feature = "rust1", since = "1.0.0")]
impl Debug for f16 {
#[inline]
Expand All @@ -239,6 +241,53 @@ impl Debug for f16 {
}
}

#[cfg(not(bootstrap))]
#[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(bootstrap))]
#[cfg(not(target_has_reliable_f16))]
#[stable(feature = "rust1", since = "1.0.0")]
impl Debug for f16 {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{:#06x}", self.to_bits())
}
}

#[cfg(not(bootstrap))]
#[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(bootstrap))]
#[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(bootstrap))]
#[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]
Expand Down
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
//
// Library features:
// tidy-alphabetical-start
#![cfg_attr(not(bootstrap), feature(cfg_target_has_reliable_f16_f128))]
#![feature(array_ptr_get)]
#![feature(asm_experimental_arch)]
#![feature(bigint_helper_methods)]
Expand Down
58 changes: 54 additions & 4 deletions library/core/src/num/dec2flt/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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 };
Expand All @@ -213,6 +218,51 @@ const fn pow2_to_pow10(a: i64) -> i64 {
res as i64
}

#[cfg(not(bootstrap))]
#[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;

Expand Down
18 changes: 18 additions & 0 deletions library/core/src/num/dec2flt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,27 @@ macro_rules! from_str_float_impl {
}
};
}

#[cfg(not(bootstrap))]
#[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.

// After the bootstrap bump this should be: `#[cfg(not(target_has_reliable_f16))`
#[cfg(any(bootstrap, all(not(bootstrap), 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
Expand Down
8 changes: 8 additions & 0 deletions library/core/src/num/flt2dec/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ pub trait DecodableFloat: RawFloat + Copy {
fn min_pos_norm_value() -> Self;
}

#[cfg(not(bootstrap))]
#[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
Expand Down
9 changes: 9 additions & 0 deletions library/coretests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,12 @@ test = true
[dev-dependencies]
rand = { version = "0.9.0", default-features = false }
rand_xorshift = { version = "0.4.0", default-features = false }

[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = [
'cfg(bootstrap)',
# Internal features aren't marked known config by default, we use these to
# gate tests.
'cfg(target_has_reliable_f16)',
]
2 changes: 2 additions & 0 deletions library/coretests/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// tidy-alphabetical-start
#![cfg_attr(not(bootstrap), feature(cfg_target_has_reliable_f16_f128))]
#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
#![cfg_attr(test, feature(cfg_match))]
#![feature(alloc_layout_extra)]
Expand Down Expand Up @@ -29,6 +30,7 @@
#![feature(exact_size_is_empty)]
#![feature(extend_one)]
#![feature(extern_types)]
#![feature(f16)]
#![feature(float_minimum_maximum)]
#![feature(flt2dec)]
#![feature(fmt_internals)]
Expand Down
15 changes: 15 additions & 0 deletions library/coretests/tests/num/dec2flt/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ 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(not(bootstrap))]
#[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() {
Expand Down
41 changes: 41 additions & 0 deletions library/coretests/tests/num/dec2flt/float.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
use core::num::dec2flt::float::RawFloat;

// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(not(bootstrap))]
#[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));
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));
Expand Down Expand Up @@ -34,6 +53,28 @@ fn test_f64_integer_decode() {

/* Sanity checks of computed magic numbers */

// FIXME(f16_f128): enable on all targets once possible.
#[test]
#[cfg(not(bootstrap))]
#[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);
Expand Down
Loading
Loading