Skip to content

Commit 3159e13

Browse files
committed
float: Add f16 parsing and printing
1 parent 37e223c commit 3159e13

File tree

12 files changed

+322
-19
lines changed

12 files changed

+322
-19
lines changed

library/core/src/fmt/float.rs

Lines changed: 2 additions & 9 deletions
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
@@ -229,15 +230,7 @@ macro_rules! floating {
229230
};
230231
}
231232

232-
floating! { f32 f64 }
233-
234-
#[stable(feature = "rust1", since = "1.0.0")]
235-
impl Debug for f16 {
236-
#[inline]
237-
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
238-
write!(f, "{:#06x}", self.to_bits())
239-
}
240-
}
233+
floating! { f16 f32 f64 }
241234

242235
#[stable(feature = "rust1", since = "1.0.0")]
243236
impl Debug for f128 {

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

Lines changed: 44 additions & 1 deletion
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
///
@@ -213,6 +213,49 @@ const fn pow2_to_pow10(a: i64) -> i64 {
213213
res as i64
214214
}
215215

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ macro_rules! from_str_float_impl {
171171
}
172172
};
173173
}
174+
175+
from_str_float_impl!(f16);
174176
from_str_float_impl!(f32);
175177
from_str_float_impl!(f64);
176178

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

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

48+
impl DecodableFloat for f16 {
49+
fn min_pos_norm_value() -> Self {
50+
f16::MIN_POSITIVE
51+
}
52+
}
53+
4854
impl DecodableFloat for f32 {
4955
fn min_pos_norm_value() -> Self {
5056
f32::MIN_POSITIVE

library/coretests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![feature(exact_size_is_empty)]
3131
#![feature(extend_one)]
3232
#![feature(extern_types)]
33+
#![feature(f16)]
3334
#![feature(float_minimum_maximum)]
3435
#![feature(flt2dec)]
3536
#![feature(fmt_internals)]

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

Lines changed: 39 additions & 0 deletions
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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
6+
// fn test_f32_integer_decode() {
7+
// assert_eq!(3.14159265359f16.integer_decode(), (13176795, -22, 1));
8+
// assert_eq!((-8573.5918555f16).integer_decode(), (8779358, -10, -1));
9+
// assert_eq!(2f16.powf(100.0).integer_decode(), (8388608, 77, 1));
10+
// assert_eq!(0f16.integer_decode(), (0, -150, 1));
11+
// assert_eq!((-0f16).integer_decode(), (0, -150, -1));
12+
// assert_eq!(f16::INFINITY.integer_decode(), (8388608, 105, 1));
13+
// assert_eq!(f16::NEG_INFINITY.integer_decode(), (8388608, 105, -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), (12582912, 105));
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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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);

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

Lines changed: 27 additions & 0 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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
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,6 +17,27 @@ 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(any(target_arch = "x86", all(target_arch = "aarch64", target_feature = "neon")))]
23+
fn compute_float_f16_rounding() {
24+
// These test near-halfway cases for single-precision floats.
25+
assert_eq!(compute_float16(0, 16777216), (151, 0));
26+
assert_eq!(compute_float16(0, 16777217), (151, 0));
27+
assert_eq!(compute_float16(0, 16777218), (151, 1));
28+
assert_eq!(compute_float16(0, 16777219), (151, 2));
29+
assert_eq!(compute_float16(0, 16777220), (151, 2));
30+
31+
// These are examples of the above tests, with
32+
// digits from the exponent shifted to the mantissa.
33+
assert_eq!(compute_float16(-10, 167772160000000000), (151, 0));
34+
assert_eq!(compute_float16(-10, 167772170000000000), (151, 0));
35+
assert_eq!(compute_float16(-10, 167772180000000000), (151, 1));
36+
// Let's check the lines to see if anything is different in table...
37+
assert_eq!(compute_float16(-10, 167772190000000000), (151, 2));
38+
assert_eq!(compute_float16(-10, 167772200000000000), (151, 2));
39+
}
40+
1441
#[test]
1542
fn compute_float_f32_rounding() {
1643
// These test near-halfway cases for single-precision floats.

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

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,92 @@ 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+
let x16: f16 = $x;
1415
let x32: f32 = $x;
1516
let x64: f64 = $x;
1617
let inputs = &[stringify!($x).into(), format!("{:?}", x64), format!("{:e}", x64)];
18+
1719
for input in inputs {
18-
assert_eq!(input.parse(), Ok(x64));
19-
assert_eq!(input.parse(), Ok(x32));
20+
assert_eq!(input.parse(), Ok(x64), "failed f64 {input}");
21+
assert_eq!(input.parse(), Ok(x32), "failed f32 {input}");
22+
assert_eq!(input.parse(), Ok(x16), "failed f16 {input}");
23+
2024
let neg_input = format!("-{input}");
21-
assert_eq!(neg_input.parse(), Ok(-x64));
22-
assert_eq!(neg_input.parse(), Ok(-x32));
25+
assert_eq!(neg_input.parse(), Ok(-x64), "failed f64 {neg_input}");
26+
assert_eq!(neg_input.parse(), Ok(-x32), "failed f32 {neg_input}");
27+
assert_eq!(neg_input.parse(), Ok(-x16), "failed f16 {neg_input}");
2328
}
2429
}};
2530
}
2631

32+
// #[test]
33+
// fn foo() {
34+
// use core::num::dec2flt::float::RawFloat;
35+
// use core::num::dec2flt::parse::parse_number;
36+
37+
// fn x<F: RawFloat + std::fmt::Display>(r: &str) {
38+
// let mut s = r.as_bytes();
39+
// let c = s[0];
40+
// let negative = c == b'-';
41+
// if c == b'-' || c == b'+' {
42+
// s = &s[1..];
43+
// }
44+
// let mut num = parse_number(s).unwrap();
45+
// num.negative = negative;
46+
// if let Some(value) = num.try_fast_path::<F>() {
47+
// // return Ok(value);
48+
// println!("fast path {value}");
49+
// return;
50+
// }
51+
52+
// let q = num.exponent;
53+
// let w = num.mantissa;
54+
55+
// println!(
56+
// "float {r} {q} {w} {q:#066b} {w:#066b} sm10 {} lg10 {} ty {} chk {}",
57+
// F::SMALLEST_POWER_OF_TEN,
58+
// F::LARGEST_POWER_OF_TEN,
59+
// std::any::type_name::<F>(),
60+
// if w == 0 || q < F::SMALLEST_POWER_OF_TEN as i64 {
61+
// "lt small 10"
62+
// } else if q > F::LARGEST_POWER_OF_TEN as i64 {
63+
// "gt big 10"
64+
// } else {
65+
// ""
66+
// }
67+
// );
68+
// }
69+
70+
// // test_literal2!(1e-20);
71+
// // test_literal2!(1e-30);
72+
// // test_literal2!(1e-40);
73+
// // test_literal2!(1e-50);
74+
// // test_literal2!(1e-60);
75+
// // test_literal2!(1e-63);
76+
// // test_literal2!(1e-64);
77+
// // test_literal2!(1e-65);
78+
// // test_literal2!(1e-66);
79+
// // test_literal2!(1e-70);
80+
// // test_literal2!(1e-70);
81+
// // test_literal2!(1e-70);
82+
// // test_literal2!(1e-70);
83+
// // test_literal2!(2.225073858507201136057409796709131975934819546351645648023426109724822222021076945516529523908135087914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630666655994938275772572015763062690663332647565300009245888316433037779791869612049497390377829704905051080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884423900265498198385487948292206894721689831099698365846814022854243330660339850886445804001034933970427567186443383770486037861622771738545623065874679014086723327636718749999999999999999999999999999999999999e-308);
84+
// // test_literal2!(1.175494140627517859246175898662808184331245864732796240031385942718174675986064769972472277004271745681762695312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-38);
85+
// // panic!();
86+
// }
87+
88+
// #[test]
89+
// fn foobar() {
90+
// use core::num::dec2flt::float::RawFloat;
91+
// panic!(
92+
// "{} {} {} {}",
93+
// <f32 as RawFloat>::LARGEST_POWER_OF_TEN,
94+
// <f32 as RawFloat>::SMALLEST_POWER_OF_TEN,
95+
// <f64 as RawFloat>::LARGEST_POWER_OF_TEN,
96+
// <f64 as RawFloat>::SMALLEST_POWER_OF_TEN,
97+
// )
98+
// }
99+
27100
#[test]
28101
fn ordinary() {
29102
test_literal!(1.0);

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,39 @@ 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+
assert_eq!(dec2flt::<f16>(s), Err(pfe_invalid()));
14+
assert_eq!(dec2flt::<f32>(s), Err(pfe_invalid()));
1315
assert_eq!(dec2flt::<f64>(s), Err(pfe_invalid()));
1416
}
1517
}
1618

1719
#[test]
1820
fn invalid_chars() {
1921
let invalid = "r,?<j";
20-
let error = Err(pfe_invalid());
2122
let valid_strings = &["123", "666.", ".1", "5e1", "7e-3", "0.0e+1"];
23+
2224
for c in invalid.chars() {
2325
for s in valid_strings {
2426
for i in 0..s.len() {
2527
let mut input = String::new();
2628
input.push_str(s);
2729
input.insert(i, c);
28-
assert!(dec2flt::<f64>(&input) == error, "did not reject invalid {:?}", input);
30+
31+
assert_eq!(
32+
dec2flt::<f16>(&input),
33+
Err(pfe_invalid()),
34+
"f16 did not reject invalid {input:?}",
35+
);
36+
assert_eq!(
37+
dec2flt::<f32>(&input),
38+
Err(pfe_invalid()),
39+
"f32 did not reject invalid {input:?}",
40+
);
41+
assert_eq!(
42+
dec2flt::<f64>(&input),
43+
Err(pfe_invalid()),
44+
"f64 did not reject invalid {input:?}",
45+
);
2946
}
3047
}
3148
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)