From 125580ae657ddcdd299889690ee56651dfb3f209 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:18:14 -0500 Subject: [PATCH 01/11] float: Update some constants to `pub(crate)` These constants can be useful outside of their current module. Make them `pub(crate)` to allow for this. --- library/core/src/num/f32.rs | 6 +++--- library/core/src/num/f64.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 4c0d95f95e562..8eba037e38fe2 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -493,13 +493,13 @@ impl f32 { pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32; /// Sign bit - const SIGN_MASK: u32 = 0x8000_0000; + pub(crate) const SIGN_MASK: u32 = 0x8000_0000; /// Exponent mask - const EXP_MASK: u32 = 0x7f80_0000; + pub(crate) const EXP_MASK: u32 = 0x7f80_0000; /// Mantissa mask - const MAN_MASK: u32 = 0x007f_ffff; + pub(crate) const MAN_MASK: u32 = 0x007f_ffff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u32 = 0x1; diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 77ca56df06705..7971e017f9175 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -492,13 +492,13 @@ impl f64 { pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64; /// Sign bit - const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + pub(crate) const SIGN_MASK: u64 = 0x8000_0000_0000_0000; /// Exponent mask - const EXP_MASK: u64 = 0x7ff0_0000_0000_0000; + pub(crate) const EXP_MASK: u64 = 0x7ff0_0000_0000_0000; /// Mantissa mask - const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff; + pub(crate) const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff; /// Minimum representable positive value (min subnormal) const TINY_BITS: u64 = 0x1; From 7fdd494573dda077cad41c881de16904b848f20b Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:14:33 -0500 Subject: [PATCH 02/11] dec2flt: Update documentation of existing methods Fix or elaborate existing float parsing documentation. This includes introducing a notation. --- library/core/src/num/dec2flt/common.rs | 14 ++++++++------ library/core/src/num/dec2flt/decimal.rs | 20 +++++++++++--------- library/core/src/num/dec2flt/mod.rs | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/library/core/src/num/dec2flt/common.rs b/library/core/src/num/dec2flt/common.rs index 4dadf406ae8c7..1646d3a95d3b1 100644 --- a/library/core/src/num/dec2flt/common.rs +++ b/library/core/src/num/dec2flt/common.rs @@ -8,12 +8,12 @@ pub(crate) trait ByteSlice { /// Writes a 64-bit integer as 8 bytes in little-endian order. fn write_u64(&mut self, value: u64); - /// Calculate the offset of a slice from another. + /// Calculate the difference in length between two slices. fn offset_from(&self, other: &Self) -> isize; /// Iteratively parse and consume digits from bytes. - /// Returns the same bytes with consumed digits being - /// elided. + /// + /// Returns the same bytes with consumed digits being elided. Breaks on invalid digits. fn parse_digits(&self, func: impl FnMut(u8)) -> &Self; } @@ -39,11 +39,11 @@ impl ByteSlice for [u8] { fn parse_digits(&self, mut func: impl FnMut(u8)) -> &Self { let mut s = self; - while let Some((c, s_next)) = s.split_first() { + while let Some((c, rest)) = s.split_first() { let c = c.wrapping_sub(b'0'); if c < 10 { func(c); - s = s_next; + s = rest; } else { break; } @@ -53,7 +53,9 @@ impl ByteSlice for [u8] { } } -/// Determine if 8 bytes are all decimal digits. +/// Determine if all characters in an 8-byte byte string (represented as a `u64`) are all decimal +/// digits. +/// /// This does not care about the order in which the bytes were loaded. pub(crate) fn is_8digits(v: u64) -> bool { let a = v.wrapping_add(0x4646_4646_4646_4646); diff --git a/library/core/src/num/dec2flt/decimal.rs b/library/core/src/num/dec2flt/decimal.rs index be9c0eccd5eb8..4e0d99d939ed7 100644 --- a/library/core/src/num/dec2flt/decimal.rs +++ b/library/core/src/num/dec2flt/decimal.rs @@ -1,4 +1,4 @@ -//! Arbitrary-precision decimal class for fallback algorithms. +//! Arbitrary-precision decimal type used by fallback algorithms. //! //! This is only used if the fast-path (native floats) and //! the Eisel-Lemire algorithm are unable to unambiguously @@ -11,6 +11,7 @@ use crate::num::dec2flt::common::{ByteSlice, is_8digits}; +/// A decimal floating-point number. #[derive(Clone)] pub struct Decimal { /// The number of significant digits in the decimal. @@ -30,18 +31,17 @@ impl Default for Decimal { } impl Decimal { - /// The maximum number of digits required to unambiguously round a float. + /// The maximum number of digits required to unambiguously round up to a 64-bit float. /// - /// For a double-precision IEEE 754 float, this required 767 digits, - /// so we store the max digits + 1. + /// For an IEEE 754 binary64 float, this required 767 digits. So we store the max digits + 1. /// /// We can exactly represent a float in radix `b` from radix 2 if /// `b` is divisible by 2. This function calculates the exact number of /// digits required to exactly represent that float. /// /// According to the "Handbook of Floating Point Arithmetic", - /// for IEEE754, with emin being the min exponent, p2 being the - /// precision, and b being the radix, the number of digits follows as: + /// for IEEE754, with `emin` being the min exponent, `p2` being the + /// precision, and `b` being the radix, the number of digits follows as: /// /// `−emin + p2 + ⌊(emin + 1) log(2, b) − log(1 − 2^(−p2), b)⌋` /// @@ -56,11 +56,12 @@ impl Decimal { /// In Python: /// `-emin + p2 + math.floor((emin+ 1)*math.log(2, b)-math.log(1-2**(-p2), b))` pub const MAX_DIGITS: usize = 768; - /// The max digits that can be exactly represented in a 64-bit integer. + /// The max decimal digits that can be exactly represented in a 64-bit integer. pub const MAX_DIGITS_WITHOUT_OVERFLOW: usize = 19; pub const DECIMAL_POINT_RANGE: i32 = 2047; - /// Append a digit to the buffer. + /// Append a digit to the buffer if it fits. + // TODO: looks like this pub fn try_add_digit(&mut self, digit: u8) { if self.num_digits < Self::MAX_DIGITS { self.digits[self.num_digits] = digit; @@ -69,6 +70,7 @@ impl Decimal { } /// Trim trailing zeros from the buffer. + // TODO: switch to `.rev().position()` pub fn trim(&mut self) { // All of the following calls to `Decimal::trim` can't panic because: // @@ -86,7 +88,7 @@ impl Decimal { pub fn round(&self) -> u64 { if self.num_digits == 0 || self.decimal_point < 0 { return 0; - } else if self.decimal_point > 18 { + } else if self.decimal_point >= Self::MAX_DIGITS_WITHOUT_OVERFLOW as i32 { return 0xFFFF_FFFF_FFFF_FFFF_u64; } let dp = self.decimal_point as usize; diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs index 6dca740684537..91bfe1bef2e77 100644 --- a/library/core/src/num/dec2flt/mod.rs +++ b/library/core/src/num/dec2flt/mod.rs @@ -3,8 +3,8 @@ //! # Problem statement //! //! We are given a decimal string such as `12.34e56`. This string consists of integral (`12`), -//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as zero -//! when missing. +//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as a +//! default value (1 or 0) when missing. //! //! We seek the IEEE 754 floating point number that is closest to the exact value of the decimal //! string. It is well-known that many decimal strings do not have terminating representations in @@ -67,6 +67,18 @@ //! "such that the exponent +/- the number of decimal digits fits into a 64 bit integer". //! Larger exponents are accepted, but we don't do arithmetic with them, they are immediately //! turned into {positive,negative} {zero,infinity}. +//! +//! # Notation +//! +//! This module uses the same notation as the Lemire paper: +//! +//! - `m`: binary mantissa; always nonnegative +//! - `p`: binary exponent; a signed integer +//! - `w`: decimal significand; always nonnegative +//! - `q`: decimal exponent; a signed integer +//! +//! This gives `m * 2^p` for the binary floating-point number, with `w * 10^q` as the decimal +//! equivalent. #![doc(hidden)] #![unstable( From 9200335be9c33d4fcabd866bcb81b998dd74de35 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 25 Aug 2024 04:15:39 -0500 Subject: [PATCH 03/11] dec2flt: Rename `Decimal` to `DecimalSeq` This module currently contains two decimal types, `Decimal` and `Number`. These names don't provide a whole lot of insight into what exactly they are, and `Number` is actually the one that is more like an expected `Decimal` type. In accordance with this, rename the existing `Decimal` to `DecimalSeq`. This highlights that it contains a sequence of decimal digits, rather than representing a floating point decimal as would be expected. --- .../dec2flt/{decimal.rs => decimal_seq.rs} | 51 ++++++++++++------- library/core/src/num/dec2flt/mod.rs | 2 +- library/core/src/num/dec2flt/slow.rs | 8 +-- library/core/tests/num/dec2flt/decimal_seq.rs | 31 +++++++++++ library/core/tests/num/dec2flt/mod.rs | 1 + 5 files changed, 71 insertions(+), 22 deletions(-) rename library/core/src/num/dec2flt/{decimal.rs => decimal_seq.rs} (93%) create mode 100644 library/core/tests/num/dec2flt/decimal_seq.rs diff --git a/library/core/src/num/dec2flt/decimal.rs b/library/core/src/num/dec2flt/decimal_seq.rs similarity index 93% rename from library/core/src/num/dec2flt/decimal.rs rename to library/core/src/num/dec2flt/decimal_seq.rs index 4e0d99d939ed7..c5fbf24b54c93 100644 --- a/library/core/src/num/dec2flt/decimal.rs +++ b/library/core/src/num/dec2flt/decimal_seq.rs @@ -11,9 +11,9 @@ use crate::num::dec2flt::common::{ByteSlice, is_8digits}; -/// A decimal floating-point number. -#[derive(Clone)] -pub struct Decimal { +/// A decimal floating-point number, represented as a sequence of decimal digits. +#[derive(Clone, Debug, PartialEq)] +pub struct DecimalSeq { /// The number of significant digits in the decimal. pub num_digits: usize, /// The offset of the decimal point in the significant digits. @@ -24,13 +24,13 @@ pub struct Decimal { pub digits: [u8; Self::MAX_DIGITS], } -impl Default for Decimal { +impl Default for DecimalSeq { fn default() -> Self { Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] } } } -impl Decimal { +impl DecimalSeq { /// The maximum number of digits required to unambiguously round up to a 64-bit float. /// /// For an IEEE 754 binary64 float, this required 767 digits. So we store the max digits + 1. @@ -61,7 +61,8 @@ impl Decimal { pub const DECIMAL_POINT_RANGE: i32 = 2047; /// Append a digit to the buffer if it fits. - // TODO: looks like this + // todo: should this return `Option`? And should it be incrementing the digit count after + // it overflows? pub fn try_add_digit(&mut self, digit: u8) { if self.num_digits < Self::MAX_DIGITS { self.digits[self.num_digits] = digit; @@ -70,13 +71,13 @@ impl Decimal { } /// Trim trailing zeros from the buffer. - // TODO: switch to `.rev().position()` + // todo: switch to `.rev().position()` After tests pass pub fn trim(&mut self) { - // All of the following calls to `Decimal::trim` can't panic because: + // All of the following calls to `DecimalSeq::trim` can't panic because: // - // 1. `parse_decimal` sets `num_digits` to a max of `Decimal::MAX_DIGITS`. + // 1. `parse_decimal` sets `num_digits` to a max of `DecimalSeq::MAX_DIGITS`. // 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`. - // 3. `left_shift` `num_digits` to a max of `Decimal::MAX_DIGITS`. + // 3. `left_shift` `num_digits` to a max of `DecimalSeq::MAX_DIGITS`. // // Trim is only called in `right_shift` and `left_shift`. debug_assert!(self.num_digits <= Self::MAX_DIGITS); @@ -91,21 +92,26 @@ impl Decimal { } else if self.decimal_point >= Self::MAX_DIGITS_WITHOUT_OVERFLOW as i32 { return 0xFFFF_FFFF_FFFF_FFFF_u64; } + let dp = self.decimal_point as usize; let mut n = 0_u64; + for i in 0..dp { n *= 10; if i < self.num_digits { n += self.digits[i] as u64; } } + let mut round_up = false; + if dp < self.num_digits { round_up = self.digits[dp] >= 5; if self.digits[dp] == 5 && dp + 1 == self.num_digits { round_up = self.truncated || ((dp != 0) && (1 & self.digits[dp - 1] != 0)) } } + if round_up { n += 1; } @@ -121,6 +127,7 @@ impl Decimal { let mut read_index = self.num_digits; let mut write_index = self.num_digits + num_new_digits; let mut n = 0_u64; + while read_index != 0 { read_index -= 1; write_index -= 1; @@ -134,6 +141,7 @@ impl Decimal { } n = quotient; } + while n > 0 { write_index -= 1; let quotient = n / 10; @@ -145,10 +153,13 @@ impl Decimal { } n = quotient; } + self.num_digits += num_new_digits; + if self.num_digits > Self::MAX_DIGITS { self.num_digits = Self::MAX_DIGITS; } + self.decimal_point += num_new_digits as i32; self.trim(); } @@ -204,8 +215,8 @@ impl Decimal { } /// Parse a big integer representation of the float as a decimal. -pub fn parse_decimal(mut s: &[u8]) -> Decimal { - let mut d = Decimal::default(); +pub fn parse_decimal_seq(mut s: &[u8]) -> DecimalSeq { + let mut d = DecimalSeq::default(); let start = s; while let Some((&b'0', s_next)) = s.split_first() { @@ -223,7 +234,7 @@ pub fn parse_decimal(mut s: &[u8]) -> Decimal { s = s_next; } } - while s.len() >= 8 && d.num_digits + 8 < Decimal::MAX_DIGITS { + while s.len() >= 8 && d.num_digits + 8 < DecimalSeq::MAX_DIGITS { let v = s.read_u64(); if !is_8digits(v) { break; @@ -235,6 +246,7 @@ pub fn parse_decimal(mut s: &[u8]) -> Decimal { s = s.parse_digits(|digit| d.try_add_digit(digit)); d.decimal_point = s.len() as i32 - first.len() as i32; } + if d.num_digits != 0 { // Ignore the trailing zeros if there are any let mut n_trailing_zeros = 0; @@ -248,11 +260,12 @@ pub fn parse_decimal(mut s: &[u8]) -> Decimal { d.decimal_point += n_trailing_zeros as i32; d.num_digits -= n_trailing_zeros; d.decimal_point += d.num_digits as i32; - if d.num_digits > Decimal::MAX_DIGITS { + if d.num_digits > DecimalSeq::MAX_DIGITS { d.truncated = true; - d.num_digits = Decimal::MAX_DIGITS; + d.num_digits = DecimalSeq::MAX_DIGITS; } } + if let Some((&ch, s_next)) = s.split_first() { if ch == b'e' || ch == b'E' { s = s_next; @@ -274,13 +287,15 @@ pub fn parse_decimal(mut s: &[u8]) -> Decimal { d.decimal_point += if neg_exp { -exp_num } else { exp_num }; } } - for i in d.num_digits..Decimal::MAX_DIGITS_WITHOUT_OVERFLOW { + + for i in d.num_digits..DecimalSeq::MAX_DIGITS_WITHOUT_OVERFLOW { d.digits[i] = 0; } + d } -fn number_of_digits_decimal_left_shift(d: &Decimal, mut shift: usize) -> usize { +fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usize { #[rustfmt::skip] const TABLE: [u16; 65] = [ 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024, @@ -345,6 +360,7 @@ fn number_of_digits_decimal_left_shift(d: &Decimal, mut shift: usize) -> usize { let pow5_a = (0x7FF & x_a) as usize; let pow5_b = (0x7FF & x_b) as usize; let pow5 = &TABLE_POW5[pow5_a..]; + for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) { if i >= d.num_digits { return num_new_digits - 1; @@ -356,5 +372,6 @@ fn number_of_digits_decimal_left_shift(d: &Decimal, mut shift: usize) -> usize { return num_new_digits; } } + num_new_digits } diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs index 91bfe1bef2e77..0f7541d521783 100644 --- a/library/core/src/num/dec2flt/mod.rs +++ b/library/core/src/num/dec2flt/mod.rs @@ -97,11 +97,11 @@ use crate::fmt; use crate::str::FromStr; mod common; -mod decimal; mod fpu; mod slow; mod table; // float is used in flt2dec, and all are used in unit tests. +pub mod decimal_seq; pub mod float; pub mod lemire; pub mod number; diff --git a/library/core/src/num/dec2flt/slow.rs b/library/core/src/num/dec2flt/slow.rs index 85d4b13284b7d..e274e1aa0aa1b 100644 --- a/library/core/src/num/dec2flt/slow.rs +++ b/library/core/src/num/dec2flt/slow.rs @@ -1,7 +1,7 @@ //! Slow, fallback algorithm for cases the Eisel-Lemire algorithm cannot round. use crate::num::dec2flt::common::BiasedFp; -use crate::num::dec2flt::decimal::{Decimal, parse_decimal}; +use crate::num::dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq}; use crate::num::dec2flt::float::RawFloat; /// Parse the significant digits and biased, binary exponent of a float. @@ -36,7 +36,7 @@ pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { let fp_zero = BiasedFp::zero_pow2(0); let fp_inf = BiasedFp::zero_pow2(F::INFINITE_POWER); - let mut d = parse_decimal(s); + let mut d = parse_decimal_seq(s); // Short-circuit if the value can only be a literal 0 or infinity. if d.num_digits == 0 || d.decimal_point < -324 { @@ -50,7 +50,7 @@ pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { let n = d.decimal_point as usize; let shift = get_shift(n); d.right_shift(shift); - if d.decimal_point < -Decimal::DECIMAL_POINT_RANGE { + if d.decimal_point < -DecimalSeq::DECIMAL_POINT_RANGE { return fp_zero; } exp2 += shift as i32; @@ -67,7 +67,7 @@ pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { get_shift((-d.decimal_point) as _) }; d.left_shift(shift); - if d.decimal_point > Decimal::DECIMAL_POINT_RANGE { + if d.decimal_point > DecimalSeq::DECIMAL_POINT_RANGE { return fp_inf; } exp2 -= shift as i32; diff --git a/library/core/tests/num/dec2flt/decimal_seq.rs b/library/core/tests/num/dec2flt/decimal_seq.rs new file mode 100644 index 0000000000000..4df80085059fd --- /dev/null +++ b/library/core/tests/num/dec2flt/decimal_seq.rs @@ -0,0 +1,31 @@ +use core::num::dec2flt::decimal_seq::{parse_decimal_seq, DecimalSeq}; + +#[test] +fn test_trim() { + let mut dec = DecimalSeq::default(); + let digits = [1, 2, 3, 4]; + + dec.digits[0..4].copy_from_slice(&digits); + dec.num_digits = 8; + dec.trim(); + + assert_eq!(dec.digits[0..4], digits); + assert_eq!(dec.num_digits, 4); +} + +#[test] +fn test_parse() { + // todo: more tests + let tests = [("1.234", [1, 2, 3, 4], 1)]; + + for (s, exp_digits, decimal_point) in tests { + let actual = parse_decimal_seq(s.as_bytes()); + let mut digits = [0; DecimalSeq::MAX_DIGITS]; + digits[..exp_digits.len()].copy_from_slice(&exp_digits); + + let expected = + DecimalSeq { num_digits: exp_digits.len(), decimal_point, truncated: false, digits }; + + assert_eq!(actual, expected); + } +} diff --git a/library/core/tests/num/dec2flt/mod.rs b/library/core/tests/num/dec2flt/mod.rs index 874e0ec7093c7..51f3017ddc79c 100644 --- a/library/core/tests/num/dec2flt/mod.rs +++ b/library/core/tests/num/dec2flt/mod.rs @@ -1,5 +1,6 @@ #![allow(overflowing_literals)] +mod decimal_seq; mod float; mod lemire; mod parse; From 0f7ce37516326d94d6088792368ce87aa87b9f25 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 25 Aug 2024 04:33:33 -0500 Subject: [PATCH 04/11] dec2flt: Rename `Number` to `Decimal` --- .../src/num/dec2flt/{number.rs => decimal.rs} | 6 +-- library/core/src/num/dec2flt/mod.rs | 2 +- library/core/src/num/dec2flt/parse.rs | 10 ++--- library/core/tests/num/dec2flt/decimal.rs | 42 +++++++++++++++++++ library/core/tests/num/dec2flt/mod.rs | 1 + library/core/tests/num/dec2flt/parse.rs | 26 ++++++------ 6 files changed, 65 insertions(+), 22 deletions(-) rename library/core/src/num/dec2flt/{number.rs => decimal.rs} (96%) create mode 100644 library/core/tests/num/dec2flt/decimal.rs diff --git a/library/core/src/num/dec2flt/number.rs b/library/core/src/num/dec2flt/decimal.rs similarity index 96% rename from library/core/src/num/dec2flt/number.rs rename to library/core/src/num/dec2flt/decimal.rs index 2538991564ae4..1ba9150a0119d 100644 --- a/library/core/src/num/dec2flt/number.rs +++ b/library/core/src/num/dec2flt/decimal.rs @@ -3,7 +3,6 @@ use crate::num::dec2flt::float::RawFloat; use crate::num::dec2flt::fpu::set_precision; -#[rustfmt::skip] const INT_POW10: [u64; 16] = [ 1, 10, @@ -23,15 +22,16 @@ const INT_POW10: [u64; 16] = [ 1000000000000000, ]; +/// A floating point number with up to 64 bits of mantissa and an `i64` exponent. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct Number { +pub struct Decimal { pub exponent: i64, pub mantissa: u64, pub negative: bool, pub many_digits: bool, } -impl Number { +impl Decimal { /// Detect if the float can be accurately reconstructed from native floats. #[inline] fn is_fast_path(&self) -> bool { diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs index 0f7541d521783..00d9a30881573 100644 --- a/library/core/src/num/dec2flt/mod.rs +++ b/library/core/src/num/dec2flt/mod.rs @@ -101,10 +101,10 @@ mod fpu; mod slow; mod table; // float is used in flt2dec, and all are used in unit tests. +pub mod decimal; pub mod decimal_seq; pub mod float; pub mod lemire; -pub mod number; pub mod parse; macro_rules! from_str_float_impl { diff --git a/library/core/src/num/dec2flt/parse.rs b/library/core/src/num/dec2flt/parse.rs index 06ee8e95fbc47..e38fedc58bec0 100644 --- a/library/core/src/num/dec2flt/parse.rs +++ b/library/core/src/num/dec2flt/parse.rs @@ -1,8 +1,8 @@ //! Functions to parse floating-point numbers. use crate::num::dec2flt::common::{ByteSlice, is_8digits}; +use crate::num::dec2flt::decimal::Decimal; use crate::num::dec2flt::float::RawFloat; -use crate::num::dec2flt::number::Number; const MIN_19DIGIT_INT: u64 = 100_0000_0000_0000_0000; @@ -100,7 +100,7 @@ fn parse_scientific(s_ref: &mut &[u8]) -> Option { /// /// This creates a representation of the float as the /// significant digits and the decimal exponent. -fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> { +fn parse_partial_number(mut s: &[u8]) -> Option<(Decimal, usize)> { debug_assert!(!s.is_empty()); // parse initial digits before dot @@ -146,7 +146,7 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> { // handle uncommon case with many digits if n_digits <= 19 { - return Some((Number { exponent, mantissa, negative: false, many_digits: false }, len)); + return Some((Decimal { exponent, mantissa, negative: false, many_digits: false }, len)); } n_digits -= 19; @@ -179,13 +179,13 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> { exponent += exp_number; } - Some((Number { exponent, mantissa, negative: false, many_digits }, len)) + Some((Decimal { exponent, mantissa, negative: false, many_digits }, len)) } /// Try to parse a non-special floating point number, /// as well as two slices with integer and fractional parts /// and the parsed exponent. -pub fn parse_number(s: &[u8]) -> Option { +pub fn parse_number(s: &[u8]) -> Option { if let Some((float, rest)) = parse_partial_number(s) { if rest == s.len() { return Some(float); diff --git a/library/core/tests/num/dec2flt/decimal.rs b/library/core/tests/num/dec2flt/decimal.rs new file mode 100644 index 0000000000000..ef2de612df827 --- /dev/null +++ b/library/core/tests/num/dec2flt/decimal.rs @@ -0,0 +1,42 @@ +use core::num::dec2flt::decimal::Decimal; + +type FPath = ((i64, u64, bool, bool), Option); + +const FPATHS_F16: &[FPath] = + &[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))]; +const FPATHS_F32: &[FPath] = + &[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))]; +const FPATHS_F64: &[FPath] = + &[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))]; + +// todo: more tests + +#[test] +fn check_fast_path_f16() { + 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::(); + + assert_eq!(actual, expected); + } +} + +#[test] +fn check_fast_path_f32() { + for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F32.iter().copied() { + let dec = Decimal { exponent, mantissa, negative, many_digits }; + let actual = dec.try_fast_path::(); + + assert_eq!(actual, expected); + } +} + +#[test] +fn check_fast_path_f64() { + for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F64.iter().copied() { + let dec = Decimal { exponent, mantissa, negative, many_digits }; + let actual = dec.try_fast_path::(); + + assert_eq!(actual, expected); + } +} diff --git a/library/core/tests/num/dec2flt/mod.rs b/library/core/tests/num/dec2flt/mod.rs index 51f3017ddc79c..a9025be5ca7f1 100644 --- a/library/core/tests/num/dec2flt/mod.rs +++ b/library/core/tests/num/dec2flt/mod.rs @@ -1,5 +1,6 @@ #![allow(overflowing_literals)] +mod decimal; mod decimal_seq; mod float; mod lemire; diff --git a/library/core/tests/num/dec2flt/parse.rs b/library/core/tests/num/dec2flt/parse.rs index 4a5d24ba7d5fa..ec705a61e13ca 100644 --- a/library/core/tests/num/dec2flt/parse.rs +++ b/library/core/tests/num/dec2flt/parse.rs @@ -1,9 +1,9 @@ -use core::num::dec2flt::number::Number; +use core::num::dec2flt::decimal::Decimal; use core::num::dec2flt::parse::parse_number; use core::num::dec2flt::{dec2flt, pfe_invalid}; -fn new_number(e: i64, m: u64) -> Number { - Number { exponent: e, mantissa: m, negative: false, many_digits: false } +fn new_dec(e: i64, m: u64) -> Decimal { + Decimal { exponent: e, mantissa: m, negative: false, many_digits: false } } #[test] @@ -31,23 +31,23 @@ fn invalid_chars() { } } -fn parse_positive(s: &[u8]) -> Option { +fn parse_positive(s: &[u8]) -> Option { parse_number(s) } #[test] fn valid() { - assert_eq!(parse_positive(b"123.456e789"), Some(new_number(786, 123456))); - assert_eq!(parse_positive(b"123.456e+789"), Some(new_number(786, 123456))); - assert_eq!(parse_positive(b"123.456e-789"), Some(new_number(-792, 123456))); - assert_eq!(parse_positive(b".050"), Some(new_number(-3, 50))); - assert_eq!(parse_positive(b"999"), Some(new_number(0, 999))); - assert_eq!(parse_positive(b"1.e300"), Some(new_number(300, 1))); - assert_eq!(parse_positive(b".1e300"), Some(new_number(299, 1))); - assert_eq!(parse_positive(b"101e-33"), Some(new_number(-33, 101))); + assert_eq!(parse_positive(b"123.456e789"), Some(new_dec(786, 123456))); + assert_eq!(parse_positive(b"123.456e+789"), Some(new_dec(786, 123456))); + assert_eq!(parse_positive(b"123.456e-789"), Some(new_dec(-792, 123456))); + assert_eq!(parse_positive(b".050"), Some(new_dec(-3, 50))); + assert_eq!(parse_positive(b"999"), Some(new_dec(0, 999))); + assert_eq!(parse_positive(b"1.e300"), Some(new_dec(300, 1))); + assert_eq!(parse_positive(b".1e300"), Some(new_dec(299, 1))); + assert_eq!(parse_positive(b"101e-33"), Some(new_dec(-33, 101))); let zeros = "0".repeat(25); let s = format!("1.5e{zeros}"); - assert_eq!(parse_positive(s.as_bytes()), Some(new_number(-1, 15))); + assert_eq!(parse_positive(s.as_bytes()), Some(new_dec(-1, 15))); } macro_rules! assert_float_result_bits_eq { From 6413fc552f8c085d906739ad01c8b69cb3d69f80 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:24:40 -0500 Subject: [PATCH 05/11] dec2flt: Rename fields to be consistent with documented notation --- library/core/src/num/dec2flt/common.rs | 13 +++++++------ library/core/src/num/dec2flt/lemire.rs | 4 ++-- library/core/src/num/dec2flt/mod.rs | 13 ++++++++----- library/core/src/num/dec2flt/slow.rs | 2 +- library/core/tests/num/dec2flt/float.rs | 8 ++++---- library/core/tests/num/dec2flt/lemire.rs | 4 ++-- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/library/core/src/num/dec2flt/common.rs b/library/core/src/num/dec2flt/common.rs index 1646d3a95d3b1..a140a311c452f 100644 --- a/library/core/src/num/dec2flt/common.rs +++ b/library/core/src/num/dec2flt/common.rs @@ -63,19 +63,20 @@ pub(crate) fn is_8digits(v: u64) -> bool { (a | b) & 0x8080_8080_8080_8080 == 0 } -/// A custom 64-bit floating point type, representing `f * 2^e`. -/// e is biased, so it be directly shifted into the exponent bits. +/// A custom 64-bit floating point type, representing `m * 2^p`. +/// p is biased, so it be directly shifted into the exponent bits. #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] pub struct BiasedFp { /// The significant digits. - pub f: u64, + pub m: u64, /// The biased, binary exponent. - pub e: i32, + pub p_biased: i32, } impl BiasedFp { + /// Represent `0 ^ p` #[inline] - pub const fn zero_pow2(e: i32) -> Self { - Self { f: 0, e } + pub const fn zero_pow2(p_biased: i32) -> Self { + Self { m: 0, p_biased } } } diff --git a/library/core/src/num/dec2flt/lemire.rs b/library/core/src/num/dec2flt/lemire.rs index 01642e1b1112a..0cc39cb9b6231 100644 --- a/library/core/src/num/dec2flt/lemire.rs +++ b/library/core/src/num/dec2flt/lemire.rs @@ -73,7 +73,7 @@ pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { mantissa += mantissa & 1; mantissa >>= 1; power2 = (mantissa >= (1_u64 << F::MANTISSA_EXPLICIT_BITS)) as i32; - return BiasedFp { f: mantissa, e: power2 }; + return BiasedFp { m: mantissa, p_biased: power2 }; } // Need to handle rounding ties. Normally, we need to round up, // but if we fall right in between and we have an even basis, we @@ -111,7 +111,7 @@ pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { // Exponent is above largest normal value, must be infinite. return fp_inf; } - BiasedFp { f: mantissa, e: power2 } + BiasedFp { m: mantissa, p_biased: power2 } } /// Calculate a base 2 exponent from a decimal exponent. diff --git a/library/core/src/num/dec2flt/mod.rs b/library/core/src/num/dec2flt/mod.rs index 00d9a30881573..4b10e1bb227dc 100644 --- a/library/core/src/num/dec2flt/mod.rs +++ b/library/core/src/num/dec2flt/mod.rs @@ -233,8 +233,8 @@ pub fn pfe_invalid() -> ParseFloatError { /// Converts a `BiasedFp` to the closest machine float type. fn biased_fp_to_float(x: BiasedFp) -> T { - let mut word = x.f; - word |= (x.e as u64) << T::MANTISSA_EXPLICIT_BITS; + let mut word = x.m; + word |= (x.p_biased as u64) << T::MANTISSA_EXPLICIT_BITS; T::from_u64_bits(word) } @@ -272,12 +272,15 @@ pub fn dec2flt(s: &str) -> Result { // redundantly using the Eisel-Lemire algorithm if it was unable to // correctly round on the first pass. let mut fp = compute_float::(num.exponent, num.mantissa); - if num.many_digits && fp.e >= 0 && fp != compute_float::(num.exponent, num.mantissa + 1) { - fp.e = -1; + if num.many_digits + && fp.p_biased >= 0 + && fp != compute_float::(num.exponent, num.mantissa + 1) + { + fp.p_biased = -1; } // Unable to correctly round the float using the Eisel-Lemire algorithm. // Fallback to a slower, but always correct algorithm. - if fp.e < 0 { + if fp.p_biased < 0 { fp = parse_long_mantissa::(s); } diff --git a/library/core/src/num/dec2flt/slow.rs b/library/core/src/num/dec2flt/slow.rs index e274e1aa0aa1b..e0c4ae117da02 100644 --- a/library/core/src/num/dec2flt/slow.rs +++ b/library/core/src/num/dec2flt/slow.rs @@ -105,5 +105,5 @@ pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { } // Zero out all the bits above the explicit mantissa bits. mantissa &= (1_u64 << F::MANTISSA_EXPLICIT_BITS) - 1; - BiasedFp { f: mantissa, e: power2 } + BiasedFp { m: mantissa, p_biased: power2 } } diff --git a/library/core/tests/num/dec2flt/float.rs b/library/core/tests/num/dec2flt/float.rs index 7a9587a18d030..40f0776e02148 100644 --- a/library/core/tests/num/dec2flt/float.rs +++ b/library/core/tests/num/dec2flt/float.rs @@ -12,8 +12,8 @@ fn test_f32_integer_decode() { // Ignore the "sign" (quiet / signalling flag) of NAN. // It can vary between runtime operations and LLVM folding. - let (nan_m, nan_e, _nan_s) = f32::NAN.integer_decode(); - assert_eq!((nan_m, nan_e), (12582912, 105)); + let (nan_m, nan_p, _nan_s) = f32::NAN.integer_decode(); + assert_eq!((nan_m, nan_p), (12582912, 105)); } #[test] @@ -28,6 +28,6 @@ fn test_f64_integer_decode() { // Ignore the "sign" (quiet / signalling flag) of NAN. // It can vary between runtime operations and LLVM folding. - let (nan_m, nan_e, _nan_s) = f64::NAN.integer_decode(); - assert_eq!((nan_m, nan_e), (6755399441055744, 972)); + let (nan_m, nan_p, _nan_s) = f64::NAN.integer_decode(); + assert_eq!((nan_m, nan_p), (6755399441055744, 972)); } diff --git a/library/core/tests/num/dec2flt/lemire.rs b/library/core/tests/num/dec2flt/lemire.rs index f71bbb7c7a318..9f228a25e46b1 100644 --- a/library/core/tests/num/dec2flt/lemire.rs +++ b/library/core/tests/num/dec2flt/lemire.rs @@ -2,12 +2,12 @@ use core::num::dec2flt::lemire::compute_float; fn compute_float32(q: i64, w: u64) -> (i32, u64) { let fp = compute_float::(q, w); - (fp.e, fp.f) + (fp.p_biased, fp.m) } fn compute_float64(q: i64, w: u64) -> (i32, u64) { let fp = compute_float::(q, w); - (fp.e, fp.f) + (fp.p_biased, fp.m) } #[test] From a67248cff1f13606d53cc328a28c65df1c605d52 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:31:40 -0500 Subject: [PATCH 06/11] dec2flt: Refactor float traits --- library/core/src/num/dec2flt/float.rs | 250 ++++++++++++++++--------- library/core/src/num/dec2flt/lemire.rs | 4 +- library/core/src/num/dec2flt/slow.rs | 2 +- src/etc/test-float-parse/src/traits.rs | 6 +- 4 files changed, 166 insertions(+), 96 deletions(-) diff --git a/library/core/src/num/dec2flt/float.rs b/library/core/src/num/dec2flt/float.rs index da57aa9a546af..d88bd688cd452 100644 --- a/library/core/src/num/dec2flt/float.rs +++ b/library/core/src/num/dec2flt/float.rs @@ -1,14 +1,56 @@ //! Helper trait for generic float types. +use core::f64; + use crate::fmt::{Debug, LowerExp}; use crate::num::FpCategory; -use crate::ops::{Add, Div, Mul, Neg}; +use crate::ops::{self, Add, Div, Mul, Neg}; + +pub trait CastInto: Copy { + fn cast(self) -> T; +} + +pub trait Integer: + Sized + + Clone + + Copy + + Debug + + ops::Shr + + ops::Shl + + ops::BitAnd + + ops::BitOr + + PartialEq + + CastInto +{ + const ZERO: Self; + const ONE: Self; +} -/// A helper trait to avoid duplicating basically all the conversion code for `f32` and `f64`. +macro_rules! int { + ($($ty:ty),+) => { + $( + impl CastInto for $ty { + fn cast(self) -> i16 { + self as i16 + } + } + + + impl Integer for $ty { + const ZERO: Self = 0; + const ONE: Self = 1; + } + )+ + } +} + +int!(u16, u32, u64); + +/// A helper trait to avoid duplicating basically all the conversion code for IEEE floats. /// /// See the parent module's doc comment for why this is necessary. /// -/// Should **never ever** be implemented for other types or be used outside the dec2flt module. +/// Should **never ever** be implemented for other types or be used outside the `dec2flt` module. #[doc(hidden)] pub trait RawFloat: Sized @@ -24,62 +66,93 @@ pub trait RawFloat: + Copy + Debug { + /// The unsigned integer with the same size as the float + type Int: Integer + Into; + + /* general constants */ + const INFINITY: Self; const NEG_INFINITY: Self; const NAN: Self; const NEG_NAN: Self; + /// Bit width of the float + const BITS: u32; + + /// Mantissa digits including the hidden bit (provided by core) + const MANTISSA_BITS: u32; + + const EXPONENT_MASK: Self::Int; + const MANTISSA_MASK: Self::Int; + /// The number of bits in the significand, *excluding* the hidden bit. - const MANTISSA_EXPLICIT_BITS: usize; - - // Round-to-even only happens for negative values of q - // when q ≥ −4 in the 64-bit case and when q ≥ −17 in - // the 32-bitcase. - // - // When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we - // have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have - // 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10. - // - // When q < 0, we have w ≥ (2m+1)×5^−q. We must have that w < 2^64 - // so (2m+1)×5^−q < 2^64. We have that 2m+1 > 2^53 (64-bit case) - // or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^−q < 2^64 - // (64-bit) and 2^24×5^−q < 2^64 (32-bit). Hence we have 5^−q < 2^11 - // or q ≥ −4 (64-bit case) and 5^−q < 2^40 or q ≥ −17 (32-bitcase). - // - // Thus we have that we only need to round ties to even when - // we have that q ∈ [−4,23](in the 64-bit case) or q∈[−17,10] - // (in the 32-bit case). In both cases,the power of five(5^|q|) - // fits in a 64-bit word. + const MANTISSA_EXPLICIT_BITS: u32 = Self::MANTISSA_BITS - 1; + + /// Bits for the exponent + const EXPONENT_BITS: u32 = Self::BITS - Self::MANTISSA_EXPLICIT_BITS - 1; + + /// Minimum exponent value `-(1 << (EXP_BITS - 1)) + 1`. + const MINIMUM_EXPONENT: i32 = -(1 << (Self::EXPONENT_BITS - 1)) + 1; + + /// Maximum exponent without overflowing to infinity + const MAXIMUM_EXPONENT: u32 = (1 << Self::EXPONENT_BITS) - 1; + + /// The exponent bias value + const EXPONENT_BIAS: u32 = Self::MAXIMUM_EXPONENT >> 1; + + /// Largest exponent value `(1 << EXP_BITS) - 1`. + const INFINITE_POWER: i32 = (1 << Self::EXPONENT_BITS) - 1; + + /// Round-to-even only happens for negative values of q + /// when q ≥ −4 in the 64-bit case and when q ≥ −17 in + /// the 32-bitcase. + /// + /// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we + /// have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have + /// 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10. + /// + /// When q < 0, we have w ≥ (2m+1)×5^−q. We must have that w < 2^64 + /// so (2m+1)×5^−q < 2^64. We have that 2m+1 > 2^53 (64-bit case) + /// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^−q < 2^64 + /// (64-bit) and 2^24×5^−q < 2^64 (32-bit). Hence we have 5^−q < 2^11 + /// or q ≥ −4 (64-bit case) and 5^−q < 2^40 or q ≥ −17 (32-bitcase). + /// + /// Thus we have that we only need to round ties to even when + /// we have that q ∈ [−4,23](in the 64-bit case) or q∈[−17,10] + /// (in the 32-bit case). In both cases,the power of five(5^|q|) + /// fits in a 64-bit word. const MIN_EXPONENT_ROUND_TO_EVEN: i32; const MAX_EXPONENT_ROUND_TO_EVEN: i32; - // Minimum exponent that for a fast path case, or `-⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋` - const MIN_EXPONENT_FAST_PATH: i64; - - // Maximum exponent that for a fast path case, or `⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋` - const MAX_EXPONENT_FAST_PATH: i64; + /// Largest decimal exponent for a non-infinite value. + /// + /// This is the max exponent in binary converted to the max exponent in decimal. Allows fast + /// pathing anything larger than `10^LARGEST_POWER_OF_TEN`, which will round to infinity. + const LARGEST_POWER_OF_TEN: i32 = + ((Self::EXPONENT_BIAS as f64 + 1.0) / f64::consts::LOG2_10) as i32; - // Maximum exponent that can be represented for a disguised-fast path case. - // This is `MAX_EXPONENT_FAST_PATH + ⌊(MANTISSA_EXPLICIT_BITS+1)/log2(10)⌋` - const MAX_EXPONENT_DISGUISED_FAST_PATH: i64; + /// Smallest decimal exponent for a non-zero value. This allows for fast pathing anything + /// smaller than `10^SMALLEST_POWER_OF_TEN`. + const SMALLEST_POWER_OF_TEN: i32 = + -(((Self::EXPONENT_BIAS + Self::MANTISSA_BITS + 64) as f64) / f64::consts::LOG2_10) as i32; - // Minimum exponent value `-(1 << (EXP_BITS - 1)) + 1`. - const MINIMUM_EXPONENT: i32; + /* Fast pathing */ - // Largest exponent value `(1 << EXP_BITS) - 1`. - const INFINITE_POWER: i32; + /// Maximum exponent for a fast path case, or `⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋` + // assuming FLT_EVAL_METHOD = 0 + const MAX_EXPONENT_FAST_PATH: i64 = + ((Self::MANTISSA_BITS as f64) / (f64::consts::LOG2_10 - 1.0)) as i64; - // Index (in bits) of the sign. - const SIGN_INDEX: usize; + /// Minimum exponent for a fast path case, or `-⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋` + const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH; - // Smallest decimal exponent for a non-zero value. - const SMALLEST_POWER_OF_TEN: i32; + /// Maximum exponent that can be represented for a disguised-fast path case. + /// This is `MAX_EXPONENT_FAST_PATH + ⌊(MANTISSA_EXPLICIT_BITS+1)/log2(10)⌋` + const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = + Self::MAX_EXPONENT_FAST_PATH + (Self::MANTISSA_BITS as f64 / f64::consts::LOG2_10) as i64; - // Largest decimal exponent for a non-infinite value. - const LARGEST_POWER_OF_TEN: i32; - - // Maximum mantissa for the fast-path (`1 << 53` for f64). - const MAX_MANTISSA_FAST_PATH: u64 = 2_u64 << Self::MANTISSA_EXPLICIT_BITS; + /// Maximum mantissa for the fast-path (`1 << 53` for f64). + const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::MANTISSA_BITS; /// Converts integer into float through an as cast. /// This is only called in the fast-path algorithm, and therefore @@ -96,27 +169,45 @@ pub trait RawFloat: /// Returns the category that this number falls into. fn classify(self) -> FpCategory; + /// Transmute to the integer representation + fn to_bits(self) -> Self::Int; + /// Returns the mantissa, exponent and sign as integers. - fn integer_decode(self) -> (u64, i16, i8); + /// + /// That is, this returns `(m, p, s)` such that `s * m * 2^p` represents the original float. + /// For 0, the exponent will be `-(EXPONENT_BIAS + MANTISSA_EXPLICIT_BITS`, which is the + /// minimum subnormal power. + 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 }; + let mut exponent: i16 = + ((bits & Self::EXPONENT_MASK) >> Self::MANTISSA_EXPLICIT_BITS).cast(); + let mantissa = if exponent == 0 { + (bits & Self::MANTISSA_MASK) << 1 + } else { + (bits & Self::MANTISSA_MASK) | (Self::Int::ONE << Self::MANTISSA_EXPLICIT_BITS) + }; + // Exponent bias + mantissa shift + exponent -= (Self::EXPONENT_BIAS + Self::MANTISSA_EXPLICIT_BITS) as i16; + (mantissa.into(), exponent, sign) + } } impl RawFloat for f32 { + type Int = u32; + const INFINITY: Self = f32::INFINITY; const NEG_INFINITY: Self = f32::NEG_INFINITY; const NAN: Self = f32::NAN; const NEG_NAN: Self = -f32::NAN; - const MANTISSA_EXPLICIT_BITS: usize = 23; + const BITS: u32 = 32; + const MANTISSA_BITS: u32 = Self::MANTISSA_DIGITS; + const EXPONENT_MASK: Self::Int = Self::EXP_MASK; + const MANTISSA_MASK: Self::Int = Self::MAN_MASK; + const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -17; const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10; - const MIN_EXPONENT_FAST_PATH: i64 = -10; // assuming FLT_EVAL_METHOD = 0 - const MAX_EXPONENT_FAST_PATH: i64 = 10; - const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 17; - const MINIMUM_EXPONENT: i32 = -127; - const INFINITE_POWER: i32 = 0xFF; - const SIGN_INDEX: usize = 31; - const SMALLEST_POWER_OF_TEN: i32 = -65; - const LARGEST_POWER_OF_TEN: i32 = 38; #[inline] fn from_u64(v: u64) -> Self { @@ -136,16 +227,8 @@ impl RawFloat for f32 { TABLE[exponent & 15] } - /// Returns the mantissa, exponent and sign as integers. - fn integer_decode(self) -> (u64, i16, i8) { - let bits = self.to_bits(); - let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 23) & 0xff) as i16; - let mantissa = - if exponent == 0 { (bits & 0x7fffff) << 1 } else { (bits & 0x7fffff) | 0x800000 }; - // Exponent bias + mantissa shift - exponent -= 127 + 23; - (mantissa as u64, exponent, sign) + fn to_bits(self) -> Self::Int { + self.to_bits() } fn classify(self) -> FpCategory { @@ -154,22 +237,20 @@ impl RawFloat for f32 { } impl RawFloat for f64 { - const INFINITY: Self = f64::INFINITY; - const NEG_INFINITY: Self = f64::NEG_INFINITY; - const NAN: Self = f64::NAN; - const NEG_NAN: Self = -f64::NAN; + type Int = u64; + + 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 = 64; + const MANTISSA_BITS: u32 = Self::MANTISSA_DIGITS; + const EXPONENT_MASK: Self::Int = Self::EXP_MASK; + const MANTISSA_MASK: Self::Int = Self::MAN_MASK; - const MANTISSA_EXPLICIT_BITS: usize = 52; const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -4; const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23; - const MIN_EXPONENT_FAST_PATH: i64 = -22; // assuming FLT_EVAL_METHOD = 0 - const MAX_EXPONENT_FAST_PATH: i64 = 22; - const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 37; - const MINIMUM_EXPONENT: i32 = -1023; - const INFINITE_POWER: i32 = 0x7FF; - const SIGN_INDEX: usize = 63; - const SMALLEST_POWER_OF_TEN: i32 = -342; - const LARGEST_POWER_OF_TEN: i32 = 308; #[inline] fn from_u64(v: u64) -> Self { @@ -190,19 +271,8 @@ impl RawFloat for f64 { TABLE[exponent & 31] } - /// Returns the mantissa, exponent and sign as integers. - fn integer_decode(self) -> (u64, i16, i8) { - let bits = self.to_bits(); - let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 }; - let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16; - let mantissa = if exponent == 0 { - (bits & 0xfffffffffffff) << 1 - } else { - (bits & 0xfffffffffffff) | 0x10000000000000 - }; - // Exponent bias + mantissa shift - exponent -= 1023 + 52; - (mantissa, exponent, sign) + fn to_bits(self) -> Self::Int { + self.to_bits() } fn classify(self) -> FpCategory { diff --git a/library/core/src/num/dec2flt/lemire.rs b/library/core/src/num/dec2flt/lemire.rs index 0cc39cb9b6231..badb2ad3471f2 100644 --- a/library/core/src/num/dec2flt/lemire.rs +++ b/library/core/src/num/dec2flt/lemire.rs @@ -38,7 +38,7 @@ pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { // Normalize our significant digits, so the most-significant bit is set. let lz = w.leading_zeros(); w <<= lz; - let (lo, hi) = compute_product_approx(q, w, F::MANTISSA_EXPLICIT_BITS + 3); + let (lo, hi) = compute_product_approx(q, w, F::MANTISSA_EXPLICIT_BITS as usize + 3); if lo == 0xFFFF_FFFF_FFFF_FFFF { // If we have failed to approximate w x 5^-q with our 128-bit value. // Since the addition of 1 could lead to an overflow which could then @@ -89,7 +89,7 @@ pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { if lo <= 1 && q >= F::MIN_EXPONENT_ROUND_TO_EVEN as i64 && q <= F::MAX_EXPONENT_ROUND_TO_EVEN as i64 - && mantissa & 3 == 1 + && mantissa & 0b11 == 0b01 && (mantissa << (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3)) == hi { // Zero the lowest bit, so we don't round up. diff --git a/library/core/src/num/dec2flt/slow.rs b/library/core/src/num/dec2flt/slow.rs index e0c4ae117da02..ed7263c3f99d6 100644 --- a/library/core/src/num/dec2flt/slow.rs +++ b/library/core/src/num/dec2flt/slow.rs @@ -87,7 +87,7 @@ pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { } // Shift the decimal to the hidden bit, and then round the value // to get the high mantissa+1 bits. - d.left_shift(F::MANTISSA_EXPLICIT_BITS + 1); + d.left_shift(F::MANTISSA_EXPLICIT_BITS as usize + 1); let mut mantissa = d.round(); if mantissa >= (1_u64 << (F::MANTISSA_EXPLICIT_BITS + 1)) { // Rounding up overflowed to the carry bit, need to diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs index f5333d63b3693..02bf2238de5a4 100644 --- a/src/etc/test-float-parse/src/traits.rs +++ b/src/etc/test-float-parse/src/traits.rs @@ -147,12 +147,12 @@ pub trait Float: } macro_rules! impl_float { - ($($fty:ty, $ity:ty, $bits:literal);+) => { + ($($fty:ty, $ity:ty);+) => { $( impl Float for $fty { type Int = $ity; type SInt = ::Signed; - const BITS: u32 = $bits; + const BITS: u32 = <$ity>::BITS; const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); @@ -168,7 +168,7 @@ macro_rules! impl_float { } } -impl_float!(f32, u32, 32; f64, u64, 64); +impl_float!(f32, u32; f64, u64; /// A test generator. Should provide an iterator that produces unique patterns to parse. /// From c15e09042849a736ed35cc85d4abb92fd47d502e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:35:33 -0500 Subject: [PATCH 07/11] dec2flt: Refactor the fast path --- library/core/src/num/dec2flt/decimal.rs | 47 ++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/library/core/src/num/dec2flt/decimal.rs b/library/core/src/num/dec2flt/decimal.rs index 1ba9150a0119d..db7176c124318 100644 --- a/library/core/src/num/dec2flt/decimal.rs +++ b/library/core/src/num/dec2flt/decimal.rs @@ -34,14 +34,15 @@ pub struct Decimal { impl Decimal { /// Detect if the float can be accurately reconstructed from native floats. #[inline] - fn is_fast_path(&self) -> bool { + fn can_use_fast_path(&self) -> bool { F::MIN_EXPONENT_FAST_PATH <= self.exponent && self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH && self.mantissa <= F::MAX_MANTISSA_FAST_PATH && !self.many_digits } - /// The fast path algorithm using machine-sized integers and floats. + /// Try turning the decimal into an exact float representation, using machine-sized integers + /// and floats. /// /// This is extracted into a separate function so that it can be attempted before constructing /// a Decimal. This only works if both the mantissa and the exponent @@ -59,30 +60,28 @@ impl Decimal { // require setting it by changing the global state (like the control word of the x87 FPU). let _cw = set_precision::(); - if self.is_fast_path::() { - let mut value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH { - // normal fast path - let value = F::from_u64(self.mantissa); - if self.exponent < 0 { - value / F::pow10_fast_path((-self.exponent) as _) - } else { - value * F::pow10_fast_path(self.exponent as _) - } + if !self.can_use_fast_path::() { + return None; + } + + let value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH { + // normal fast path + let value = F::from_u64(self.mantissa); + if self.exponent < 0 { + value / F::pow10_fast_path((-self.exponent) as _) } else { - // disguised fast path - let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH; - let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?; - if mantissa > F::MAX_MANTISSA_FAST_PATH { - return None; - } - F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _) - }; - if self.negative { - value = -value; + value * F::pow10_fast_path(self.exponent as _) } - Some(value) } else { - None - } + // disguised fast path + let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH; + let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?; + if mantissa > F::MAX_MANTISSA_FAST_PATH { + return None; + } + F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _) + }; + + if self.negative { Some(-value) } else { Some(value) } } } From a0778ead6ee96661e004cc17750f7b6d9f6f8f5c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 24 Aug 2024 22:29:39 -0500 Subject: [PATCH 08/11] float: Add `f16` parsing and printing --- library/core/src/fmt/float.rs | 11 +- library/core/src/num/dec2flt/float.rs | 42 +++++++ library/core/src/num/dec2flt/mod.rs | 2 + library/core/src/num/flt2dec/decoder.rs | 6 + library/core/tests/lib.rs | 1 + library/core/tests/num/dec2flt/float.rs | 16 +++ library/core/tests/num/dec2flt/lemire.rs | 24 ++++ library/core/tests/num/dec2flt/mod.rs | 140 ++++++++++++++++++++++- library/core/tests/num/dec2flt/parse.rs | 21 +++- library/core/tests/num/flt2dec/mod.rs | 101 ++++++++++++++++ src/etc/test-float-parse/src/traits.rs | 6 +- 11 files changed, 352 insertions(+), 18 deletions(-) diff --git a/library/core/src/fmt/float.rs b/library/core/src/fmt/float.rs index 3f10158193d76..6826e0b955713 100644 --- a/library/core/src/fmt/float.rs +++ b/library/core/src/fmt/float.rs @@ -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 @@ -229,15 +230,7 @@ macro_rules! floating { }; } -floating! { f32 f64 } - -#[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()) - } -} +floating! { f16 f32 f64 } #[stable(feature = "rust1", since = "1.0.0")] impl Debug for f128 { diff --git a/library/core/src/num/dec2flt/float.rs b/library/core/src/num/dec2flt/float.rs index d88bd688cd452..f3aad0ec01ba5 100644 --- a/library/core/src/num/dec2flt/float.rs +++ b/library/core/src/num/dec2flt/float.rs @@ -193,6 +193,48 @@ pub trait RawFloat: } } +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 MANTISSA_BITS: u32 = Self::MANTISSA_DIGITS; + const EXPONENT_MASK: Self::Int = Self::EXP_MASK; + const MANTISSA_MASK: Self::Int = Self::MAN_MASK; + + const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -22; + const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 5; + + #[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 & 0xFF) 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 & 15] + } + + 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 4b10e1bb227dc..18d8c8ec3b083 100644 --- a/library/core/src/num/dec2flt/mod.rs +++ b/library/core/src/num/dec2flt/mod.rs @@ -171,6 +171,8 @@ macro_rules! from_str_float_impl { } }; } + +from_str_float_impl!(f16); from_str_float_impl!(f32); from_str_float_impl!(f64); diff --git a/library/core/src/num/flt2dec/decoder.rs b/library/core/src/num/flt2dec/decoder.rs index 40b3aae24a536..a8ee0c6b37928 100644 --- a/library/core/src/num/flt2dec/decoder.rs +++ b/library/core/src/num/flt2dec/decoder.rs @@ -45,6 +45,12 @@ pub trait DecodableFloat: RawFloat + Copy { fn min_pos_norm_value() -> Self; } +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 diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index a4a794691fe38..fc6728c07e222 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -28,6 +28,7 @@ #![feature(error_generic_member_access)] #![feature(exact_size_is_empty)] #![feature(extern_types)] +#![feature(f16)] #![feature(float_minimum_maximum)] #![feature(flt2dec)] #![feature(fmt_internals)] diff --git a/library/core/tests/num/dec2flt/float.rs b/library/core/tests/num/dec2flt/float.rs index 40f0776e02148..b494e13afd74a 100644 --- a/library/core/tests/num/dec2flt/float.rs +++ b/library/core/tests/num/dec2flt/float.rs @@ -1,5 +1,21 @@ use core::num::dec2flt::float::RawFloat; +#[test] +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(4.0).integer_decode(), (1024, -6, 1)); + assert_eq!(0f16.integer_decode(), (0, -25, 1)); + assert_eq!((-0f16).integer_decode(), (0, -25, -1)); + assert_eq!(f16::INFINITY.integer_decode(), (1024, 6, 1)); + assert_eq!(f16::NEG_INFINITY.integer_decode(), (1024, 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)); diff --git a/library/core/tests/num/dec2flt/lemire.rs b/library/core/tests/num/dec2flt/lemire.rs index 9f228a25e46b1..731403b0bee19 100644 --- a/library/core/tests/num/dec2flt/lemire.rs +++ b/library/core/tests/num/dec2flt/lemire.rs @@ -1,5 +1,10 @@ use core::num::dec2flt::lemire::compute_float; +// fn compute_float16(q: i64, w: u64) -> (i32, u64) { +// let fp = compute_float::(q, w); +// (fp.p_biased, fp.m) +// } + fn compute_float32(q: i64, w: u64) -> (i32, u64) { let fp = compute_float::(q, w); (fp.p_biased, fp.m) @@ -10,6 +15,25 @@ fn compute_float64(q: i64, w: u64) -> (i32, u64) { (fp.p_biased, fp.m) } +// #[test] +// fn compute_float_f16_rounding() { +// // These test near-halfway cases for single-precision floats. +// assert_eq!(compute_float16(0, 16777216), (151, 0)); +// assert_eq!(compute_float16(0, 16777217), (151, 0)); +// assert_eq!(compute_float16(0, 16777218), (151, 1)); +// assert_eq!(compute_float16(0, 16777219), (151, 2)); +// assert_eq!(compute_float16(0, 16777220), (151, 2)); + +// // These are examples of the above tests, with +// // digits from the exponent shifted to the mantissa. +// assert_eq!(compute_float16(-10, 167772160000000000), (151, 0)); +// assert_eq!(compute_float16(-10, 167772170000000000), (151, 0)); +// assert_eq!(compute_float16(-10, 167772180000000000), (151, 1)); +// // Let's check the lines to see if anything is different in table... +// assert_eq!(compute_float16(-10, 167772190000000000), (151, 2)); +// assert_eq!(compute_float16(-10, 167772200000000000), (151, 2)); +// } + #[test] fn compute_float_f32_rounding() { // These test near-halfway cases for single-precision floats. diff --git a/library/core/tests/num/dec2flt/mod.rs b/library/core/tests/num/dec2flt/mod.rs index a9025be5ca7f1..0c01ee1b915ec 100644 --- a/library/core/tests/num/dec2flt/mod.rs +++ b/library/core/tests/num/dec2flt/mod.rs @@ -11,19 +11,92 @@ mod parse; // Requires a *polymorphic literal*, i.e., one that can serve as f64 as well as f32. macro_rules! test_literal { ($x: expr) => {{ + 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}"); + 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}"); + assert_eq!(neg_input.parse(), Ok(-x16), "failed f16 {neg_input}"); } }}; } +// #[test] +// fn foo() { +// use core::num::dec2flt::float::RawFloat; +// use core::num::dec2flt::parse::parse_number; + +// fn x(r: &str) { +// let mut s = r.as_bytes(); +// let c = s[0]; +// let negative = c == b'-'; +// if c == b'-' || c == b'+' { +// s = &s[1..]; +// } +// let mut num = parse_number(s).unwrap(); +// num.negative = negative; +// if let Some(value) = num.try_fast_path::() { +// // return Ok(value); +// println!("fast path {value}"); +// return; +// } + +// let q = num.exponent; +// let w = num.mantissa; + +// println!( +// "float {r} {q} {w} {q:#066b} {w:#066b} sm10 {} lg10 {} ty {} chk {}", +// F::SMALLEST_POWER_OF_TEN, +// F::LARGEST_POWER_OF_TEN, +// std::any::type_name::(), +// if w == 0 || q < F::SMALLEST_POWER_OF_TEN as i64 { +// "lt small 10" +// } else if q > F::LARGEST_POWER_OF_TEN as i64 { +// "gt big 10" +// } else { +// "" +// } +// ); +// } + +// // test_literal2!(1e-20); +// // test_literal2!(1e-30); +// // test_literal2!(1e-40); +// // test_literal2!(1e-50); +// // test_literal2!(1e-60); +// // test_literal2!(1e-63); +// // test_literal2!(1e-64); +// // test_literal2!(1e-65); +// // test_literal2!(1e-66); +// // test_literal2!(1e-70); +// // test_literal2!(1e-70); +// // test_literal2!(1e-70); +// // test_literal2!(1e-70); +// // test_literal2!(2.225073858507201136057409796709131975934819546351645648023426109724822222021076945516529523908135087914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630666655994938275772572015763062690663332647565300009245888316433037779791869612049497390377829704905051080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884423900265498198385487948292206894721689831099698365846814022854243330660339850886445804001034933970427567186443383770486037861622771738545623065874679014086723327636718749999999999999999999999999999999999999e-308); +// // test_literal2!(1.175494140627517859246175898662808184331245864732796240031385942718174675986064769972472277004271745681762695312500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-38); +// // panic!(); +// } + +// #[test] +// fn foobar() { +// use core::num::dec2flt::float::RawFloat; +// panic!( +// "{} {} {} {}", +// ::LARGEST_POWER_OF_TEN, +// ::SMALLEST_POWER_OF_TEN, +// ::LARGEST_POWER_OF_TEN, +// ::SMALLEST_POWER_OF_TEN, +// ) +// } + #[test] fn ordinary() { test_literal!(1.0); @@ -34,6 +107,65 @@ fn ordinary() { test_literal!(2.2250738585072014e-308); } +#[test] +fn stats() { + // use + use core::num::dec2flt::float::RawFloat; + dbg!( + f16::BITS, + f16::MANTISSA_BITS, + f16::MANTISSA_EXPLICIT_BITS, + f16::EXPONENT_BITS, + f16::MAX_EXPONENT_FAST_PATH, + f16::MIN_EXPONENT_FAST_PATH, + f16::MIN_EXPONENT_ROUND_TO_EVEN, + f16::MAX_EXPONENT_ROUND_TO_EVEN, + f16::MINIMUM_EXPONENT, + f16::MAXIMUM_EXPONENT, + f16::EXPONENT_BIAS, + f16::INFINITE_POWER, + f16::LARGEST_POWER_OF_TEN, + f16::SMALLEST_POWER_OF_TEN, + f16::MAX_MANTISSA_FAST_PATH + ); + dbg!( + f32::BITS, + f32::MANTISSA_BITS, + f32::MANTISSA_EXPLICIT_BITS, + f32::EXPONENT_BITS, + f32::MAX_EXPONENT_FAST_PATH, + f32::MIN_EXPONENT_FAST_PATH, + f32::MIN_EXPONENT_ROUND_TO_EVEN, + f32::MAX_EXPONENT_ROUND_TO_EVEN, + f32::MINIMUM_EXPONENT, + f32::MAXIMUM_EXPONENT, + f32::EXPONENT_BIAS, + f32::INFINITE_POWER, + f32::LARGEST_POWER_OF_TEN, + f32::SMALLEST_POWER_OF_TEN, + f32::MAX_MANTISSA_FAST_PATH + ); + dbg!( + f64::BITS, + f64::MANTISSA_BITS, + f64::MANTISSA_EXPLICIT_BITS, + f64::EXPONENT_BITS, + f64::MAX_EXPONENT_FAST_PATH, + f64::MIN_EXPONENT_FAST_PATH, + f64::MIN_EXPONENT_ROUND_TO_EVEN, + f64::MAX_EXPONENT_ROUND_TO_EVEN, + f64::MINIMUM_EXPONENT, + f64::MAXIMUM_EXPONENT, + f64::EXPONENT_BIAS, + f64::INFINITE_POWER, + f64::LARGEST_POWER_OF_TEN, + f64::SMALLEST_POWER_OF_TEN, + f64::MAX_MANTISSA_FAST_PATH + ); + + panic!(); +} + #[test] fn special_code_paths() { test_literal!(36893488147419103229.0); // 2^65 - 3, triggers half-to-even with even significand diff --git a/library/core/tests/num/dec2flt/parse.rs b/library/core/tests/num/dec2flt/parse.rs index ec705a61e13ca..bca14596e2c68 100644 --- a/library/core/tests/num/dec2flt/parse.rs +++ b/library/core/tests/num/dec2flt/parse.rs @@ -10,6 +10,8 @@ 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 { + assert_eq!(dec2flt::(s), Err(pfe_invalid())); + assert_eq!(dec2flt::(s), Err(pfe_invalid())); assert_eq!(dec2flt::(s), Err(pfe_invalid())); } } @@ -17,15 +19,30 @@ fn missing_pieces() { #[test] fn invalid_chars() { let invalid = "r,?(&input) == error, "did not reject invalid {:?}", input); + + assert_eq!( + dec2flt::(&input), + Err(pfe_invalid()), + "f16 did not reject invalid {input:?}", + ); + assert_eq!( + dec2flt::(&input), + Err(pfe_invalid()), + "f32 did not reject invalid {input:?}", + ); + assert_eq!( + dec2flt::(&input), + Err(pfe_invalid()), + "f64 did not reject invalid {input:?}", + ); } } } diff --git a/library/core/tests/num/flt2dec/mod.rs b/library/core/tests/num/flt2dec/mod.rs index 3d82522481316..300194811329d 100644 --- a/library/core/tests/num/flt2dec/mod.rs +++ b/library/core/tests/num/flt2dec/mod.rs @@ -75,6 +75,10 @@ macro_rules! try_fixed { }) } +// 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 +180,12 @@ trait TestableFloat: DecodableFloat + fmt::Display { fn ldexpi(f: i64, exp: isize) -> Self; } +// 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() @@ -226,6 +236,97 @@ 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 +// pub fn f16_shortest_sanity_test(mut f: F) +// where +// F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), +// { +// // 0.0999999940395355224609375 +// // 0.100000001490116119384765625 +// // 0.10000000894069671630859375 +// check_shortest!(f(0.1f16) => b"1", 0); + +// // 0.333333313465118408203125 +// // 0.3333333432674407958984375 (1/3 in the default rounding) +// // 0.33333337306976318359375 +// check_shortest!(f(1.0f16/3.0) => b"33333334", 0); + +// // 10^1 * 0.31415917873382568359375 +// // 10^1 * 0.31415920257568359375 +// // 10^1 * 0.31415922641754150390625 +// check_shortest!(f(3.141592f16) => b"3141592", 1); + +// // 10^18 * 0.31415916243714048 +// // 10^18 * 0.314159196796878848 +// // 10^18 * 0.314159231156617216 +// check_shortest!(f(3.141592e17f16) => b"3141592", 18); + +// // regression test for decoders +// // 10^8 * 0.3355443 +// // 10^8 * 0.33554432 +// // 10^8 * 0.33554436 +// check_shortest!(f(ldexp_f16(1.0, 25)) => b"33554432", 8); + +// // 10^39 * 0.340282326356119256160033759537265639424 +// // 10^39 * 0.34028234663852885981170418348451692544 +// // 10^39 * 0.340282366920938463463374607431768211456 +// check_shortest!(f(f16::MAX) => b"34028235", 39); + +// // 10^-37 * 0.1175494210692441075487029444849287348827... +// // 10^-37 * 0.1175494350822287507968736537222245677818... +// // 10^-37 * 0.1175494490952133940450443629595204006810... +// check_shortest!(f(f16::MIN_POSITIVE) => b"11754944", -37); + +// // 10^-44 * 0 +// // 10^-44 * 0.1401298464324817070923729583289916131280... +// // 10^-44 * 0.2802596928649634141847459166579832262560... +// let minf16 = ldexp_f16(1.0, -149); +// check_shortest!(f(minf16) => b"1", -44); +// } + +// pub fn f16_exact_sanity_test(mut f: F) +// where +// F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit], i16) -> (&'a [u8], i16), +// { +// let minf16 = ldexp_f16(1.0, -149); + +// check_exact!(f(0.1f16) => b"100000001490116119384765625 ", 0); +// check_exact!(f(0.5f16) => b"5 ", 0); +// check_exact!(f(1.0f16/3.0) => b"3333333432674407958984375 ", 0); +// check_exact!(f(3.141592f16) => b"31415920257568359375 ", 1); +// check_exact!(f(3.141592e17f16) => b"314159196796878848 ", 18); +// check_exact!(f(f16::MAX) => b"34028234663852885981170418348451692544 ", 39); +// check_exact!(f(f16::MIN_POSITIVE) => b"1175494350822287507968736537222245677818", -37); +// check_exact!(f(minf16) => b"1401298464324817070923729583289916131280", -44); + +// // [1], Table 16: Stress Inputs for Converting 24-bit Binary to Decimal, < 1/2 ULP +// check_exact_one!(f(12676506, -102; f16) => b"2", -23); +// check_exact_one!(f(12676506, -103; f16) => b"12", -23); +// check_exact_one!(f(15445013, 86; f16) => b"119", 34); +// check_exact_one!(f(13734123, -138; f16) => b"3941", -34); +// check_exact_one!(f(12428269, -130; f16) => b"91308", -32); +// check_exact_one!(f(15334037, -146; f16) => b"171900", -36); +// check_exact_one!(f(11518287, -41; f16) => b"5237910", -5); +// check_exact_one!(f(12584953, -145; f16) => b"28216440", -36); +// check_exact_one!(f(15961084, -125; f16) => b"375243281", -30); +// check_exact_one!(f(14915817, -146; f16) => b"1672120916", -36); +// check_exact_one!(f(10845484, -102; f16) => b"21388945814", -23); +// check_exact_one!(f(16431059, -61; f16) => b"712583594561", -11); + +// // [1], Table 17: Stress Inputs for Converting 24-bit Binary to Decimal, > 1/2 ULP +// check_exact_one!(f(16093626, 69; f16) => b"1", 29); +// check_exact_one!(f( 9983778, 25; f16) => b"34", 15); +// check_exact_one!(f(12745034, 104; f16) => b"259", 39); +// check_exact_one!(f(12706553, 72; f16) => b"6001", 29); +// check_exact_one!(f(11005028, 45; f16) => b"38721", 21); +// check_exact_one!(f(15059547, 71; f16) => b"355584", 29); +// check_exact_one!(f(16015691, -99; f16) => b"2526831", -22); +// check_exact_one!(f( 8667859, 56; f16) => b"62458507", 24); +// check_exact_one!(f(14855922, -82; f16) => b"307213267", -17); +// check_exact_one!(f(14855922, -83; f16) => b"1536066333", -17); +// check_exact_one!(f(10144164, -110; f16) => b"78147796834", -26); +// check_exact_one!(f(13248074, 95; f16) => b"524810279937", 36); +// } + pub fn f32_shortest_sanity_test(mut f: F) where F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit]) -> (&'a [u8], i16), diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs index 02bf2238de5a4..f5333d63b3693 100644 --- a/src/etc/test-float-parse/src/traits.rs +++ b/src/etc/test-float-parse/src/traits.rs @@ -147,12 +147,12 @@ pub trait Float: } macro_rules! impl_float { - ($($fty:ty, $ity:ty);+) => { + ($($fty:ty, $ity:ty, $bits:literal);+) => { $( impl Float for $fty { type Int = $ity; type SInt = ::Signed; - const BITS: u32 = <$ity>::BITS; + const BITS: u32 = $bits; const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); @@ -168,7 +168,7 @@ macro_rules! impl_float { } } -impl_float!(f32, u32; f64, u64; +impl_float!(f32, u32, 32; f64, u64, 64); /// A test generator. Should provide an iterator that produces unique patterns to parse. /// From 04a25f803dce1ecd90f477b92c76b38a75c1d3a9 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 25 Aug 2024 01:30:17 -0500 Subject: [PATCH 09/11] float: Add `f16` to `test-float-parse` --- src/bootstrap/src/core/build_steps/test.rs | 4 +++- src/etc/test-float-parse/src/gen/subnorm.rs | 9 +++++++-- src/etc/test-float-parse/src/lib.rs | 3 +++ src/etc/test-float-parse/src/traits.rs | 8 ++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 30fdea7e19e51..b8679f5c9e69d 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -3656,7 +3656,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, @@ -3666,6 +3666,7 @@ impl Step for TestFloatParse { SourceType::InTree, &[], ); + cargo_test.allow_features("f16"); run_cargo_test( cargo_test, @@ -3689,6 +3690,7 @@ impl Step for TestFloatParse { SourceType::InTree, &[], ); + cargo_run.allow_features("f16"); 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/etc/test-float-parse/src/gen/subnorm.rs b/src/etc/test-float-parse/src/gen/subnorm.rs index 4fe3b90a3ddf4..44094737f43fb 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 71b1aa0667109..48e8440ded37e 100644 --- a/src/etc/test-float-parse/src/lib.rs +++ b/src/etc/test-float-parse/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(f16)] + mod traits; mod ui; mod validate; @@ -114,6 +116,7 @@ pub fn register_tests(cfg: &Config) -> Vec { let mut tests = Vec::new(); // Register normal generators for all floats. + register_float::(&mut tests, cfg); register_float::(&mut tests, cfg); register_float::(&mut tests, cfg); diff --git a/src/etc/test-float-parse/src/traits.rs b/src/etc/test-float-parse/src/traits.rs index f5333d63b3693..34a873f8ff71a 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: @@ -147,12 +147,12 @@ pub trait Float: } macro_rules! impl_float { - ($($fty:ty, $ity:ty, $bits:literal);+) => { + ($($fty:ty, $ity:ty);+) => { $( impl Float for $fty { type Int = $ity; type SInt = ::Signed; - const BITS: u32 = $bits; + const BITS: u32 = <$ity>::BITS; const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); @@ -168,7 +168,7 @@ macro_rules! impl_float { } } -impl_float!(f32, u32, 32; f64, u64, 64); +impl_float!(f16, u16; f32, u32; f64, u64); /// A test generator. Should provide an iterator that produces unique patterns to parse. /// From 7efacc8a9383212f9c48dba80478725044b26534 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 25 Aug 2024 06:12:06 -0500 Subject: [PATCH 10/11] dec2flt: Refactor the slow module --- library/core/tests/num/dec2flt/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/tests/num/dec2flt/mod.rs b/library/core/tests/num/dec2flt/mod.rs index 0c01ee1b915ec..6fabf40717f18 100644 --- a/library/core/tests/num/dec2flt/mod.rs +++ b/library/core/tests/num/dec2flt/mod.rs @@ -5,6 +5,7 @@ mod decimal_seq; mod float; mod lemire; mod parse; +mod slow; // Take a float literal, turn it into a string in various ways (that are all trusted // to be correct) and see if those strings are parsed back to the value of the literal. From a6d693d63a574a58d04de319df9b0abf1f2bc47c Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 31 Aug 2024 21:22:27 -0500 Subject: [PATCH 11/11] Add slow test file --- library/core/tests/num/dec2flt/slow.rs | 1 + 1 file changed, 1 insertion(+) create mode 100644 library/core/tests/num/dec2flt/slow.rs diff --git a/library/core/tests/num/dec2flt/slow.rs b/library/core/tests/num/dec2flt/slow.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/library/core/tests/num/dec2flt/slow.rs @@ -0,0 +1 @@ +