From 02ac1909090fa115fef35fa454cdad80839d19f0 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 31 Jan 2026 02:10:07 -0600 Subject: [PATCH 1/3] dec2flt: Rename `Integer` to `Int` This is more consistent with what the trait is called elsewhere in tree (specifically compiler-builtins and test-float-parse). --- library/core/src/num/imp/dec2flt/float.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/num/imp/dec2flt/float.rs b/library/core/src/num/imp/dec2flt/float.rs index 21aabdc8addb4..1ccc50fde5904 100644 --- a/library/core/src/num/imp/dec2flt/float.rs +++ b/library/core/src/num/imp/dec2flt/float.rs @@ -12,7 +12,7 @@ pub trait CastInto: Copy { } /// Collection of traits that allow us to be generic over integer size. -pub trait Integer: +pub trait Int: Sized + Clone + Copy @@ -37,7 +37,7 @@ macro_rules! int { } } - impl Integer for $ty { + impl Int for $ty { const ZERO: Self = 0; const ONE: Self = 1; } @@ -68,7 +68,7 @@ pub trait RawFloat: + Debug { /// The unsigned integer with the same size as the float - type Int: Integer + Into; + type Int: Int + Into; /* general constants */ From b07c0439a4d2da5c67ab50ceff6415071049a474 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 31 Jan 2026 02:57:22 -0600 Subject: [PATCH 2/3] dec2flt: Split up the `RawFloat` trait `RawFloat` is currently used specifically for the implementation of the lemire algorithm, but it is useful for more than that. Split it into three different traits: * `Float`: Anything that is reasonably applicable to all floating point types. * `FloatExt`: Items that should be part of `Float` but don't work for all float types. This will eventually be merged back into `Float`. * `Lemire`: Items that are specific to the Lemire algorithm. --- library/core/src/num/imp/dec2flt/decimal.rs | 9 +- library/core/src/num/imp/dec2flt/float.rs | 161 ++++++++++-------- library/core/src/num/imp/dec2flt/lemire.rs | 4 +- library/core/src/num/imp/dec2flt/mod.rs | 6 +- library/core/src/num/imp/dec2flt/parse.rs | 4 +- library/core/src/num/imp/dec2flt/slow.rs | 4 +- library/core/src/num/imp/flt2dec/decoder.rs | 4 +- library/coretests/tests/num/dec2flt/float.rs | 95 ++++++----- library/coretests/tests/num/dec2flt/lemire.rs | 2 +- 9 files changed, 156 insertions(+), 133 deletions(-) diff --git a/library/core/src/num/imp/dec2flt/decimal.rs b/library/core/src/num/imp/dec2flt/decimal.rs index 27a53d4b9e75b..583f6bcf3302c 100644 --- a/library/core/src/num/imp/dec2flt/decimal.rs +++ b/library/core/src/num/imp/dec2flt/decimal.rs @@ -1,9 +1,8 @@ //! Representation of a float as the significant digits and exponent. -use dec2flt::float::RawFloat; -use dec2flt::fpu::set_precision; - use crate::num::imp::dec2flt; +use dec2flt::float::Lemire; +use dec2flt::fpu::set_precision; const INT_POW10: [u64; 16] = [ 1, @@ -36,7 +35,7 @@ pub struct Decimal { impl Decimal { /// Detect if the float can be accurately reconstructed from native floats. #[inline] - fn can_use_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 @@ -53,7 +52,7 @@ impl Decimal { /// /// There is an exception: disguised fast-path cases, where we can shift /// powers-of-10 from the exponent to the significant digits. - pub fn try_fast_path(&self) -> Option { + pub fn try_fast_path(&self) -> Option { // Here we need to work around . // The fast path crucially depends on arithmetic being rounded to the correct number of bits // without any intermediate rounding. On x86 (without SSE or SSE2) this requires the precision diff --git a/library/core/src/num/imp/dec2flt/float.rs b/library/core/src/num/imp/dec2flt/float.rs index 1ccc50fde5904..da581a457f495 100644 --- a/library/core/src/num/imp/dec2flt/float.rs +++ b/library/core/src/num/imp/dec2flt/float.rs @@ -48,12 +48,8 @@ macro_rules! int { 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. #[doc(hidden)] -pub trait RawFloat: +pub trait Float: Sized + Div + Neg @@ -128,8 +124,6 @@ pub trait RawFloat: const MIN_EXPONENT_ROUND_TO_EVEN: i32; const MAX_EXPONENT_ROUND_TO_EVEN: i32; - /* limits related to Fast pathing */ - /// Largest decimal exponent for a non-infinite value. /// /// This is the max exponent in binary converted to the max exponent in decimal. Allows fast @@ -151,41 +145,19 @@ pub trait RawFloat: /// compile time since intermediates exceed the range of an `f64`. const SMALLEST_POWER_OF_TEN: i32; - /// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋` - // assuming FLT_EVAL_METHOD = 0 - const MAX_EXPONENT_FAST_PATH: i64 = { - let log2_5 = f64::consts::LOG2_10 - 1.0; - (Self::SIG_TOTAL_BITS as f64 / log2_5) as i64 - }; - - /// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋` - const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH; - - /// Maximum exponent that can be represented for a disguised-fast path case. - /// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋` - const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = - Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64; - - /// Maximum mantissa for the fast-path (`1 << 53` for f64). - const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS; - - /// Converts integer into float through an as cast. - /// This is only called in the fast-path algorithm, and therefore - /// will not lose precision, since the value will always have - /// only if the value is <= Self::MAX_MANTISSA_FAST_PATH. - fn from_u64(v: u64) -> Self; - - /// Performs a raw transmutation from an integer. - fn from_u64_bits(v: u64) -> Self; - - /// Gets a small power-of-ten for fast-path multiplication. - fn pow10_fast_path(exponent: usize) -> Self; - /// Returns the category that this number falls into. fn classify(self) -> FpCategory; /// Transmute to the integer representation fn to_bits(self) -> Self::Int; +} + +/// Items that ideally would be on `Float`, but don't apply to all float types because they +/// rely on the mantissa fitting into a `u64` (which isn't true for `f128`). +#[doc(hidden)] +pub trait FloatExt: Float { + /// Performs a raw transmutation from an integer. + fn from_u64_bits(v: u64) -> Self; /// Returns the mantissa, exponent and sign as integers. /// @@ -212,6 +184,41 @@ pub trait RawFloat: } } +/// Extension to `Float` that are necessary for parsing using the Lemire method. +/// +/// See the parent module's doc comment for why this is necessary. +/// +/// Not intended for use outside of the `dec2flt` module. +#[doc(hidden)] +pub trait Lemire: FloatExt { + /// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋` + // assuming FLT_EVAL_METHOD = 0 + const MAX_EXPONENT_FAST_PATH: i64 = { + let log2_5 = f64::consts::LOG2_10 - 1.0; + (Self::SIG_TOTAL_BITS as f64 / log2_5) as i64 + }; + + /// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋` + const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH; + + /// Maximum exponent that can be represented for a disguised-fast path case. + /// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋` + const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = + Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64; + + /// Maximum mantissa for the fast-path (`1 << 53` for f64). + const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS; + + /// Gets a small power-of-ten for fast-path multiplication. + fn pow10_fast_path(exponent: usize) -> Self; + + /// Converts integer into float through an as cast. + /// This is only called in the fast-path algorithm, and therefore + /// will not lose precision, since the value will always have + /// only if the value is <= Self::MAX_MANTISSA_FAST_PATH. + fn from_u64(v: u64) -> Self; +} + /// Solve for `b` in `10^b = 2^a` const fn pow2_to_pow10(a: i64) -> i64 { let res = (a as f64) / f64::consts::LOG2_10; @@ -219,7 +226,7 @@ const fn pow2_to_pow10(a: i64) -> i64 { } #[cfg(target_has_reliable_f16)] -impl RawFloat for f16 { +impl Float for f16 { type Int = u16; const INFINITY: Self = Self::INFINITY; @@ -236,33 +243,39 @@ impl RawFloat for f16 { 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 _ + fn to_bits(self) -> Self::Int { + self.to_bits() + } + + fn classify(self) -> FpCategory { + self.classify() } +} +#[cfg(target_has_reliable_f16)] +impl FloatExt for f16 { #[inline] fn from_u64_bits(v: u64) -> Self { Self::from_bits((v & 0xFFFF) as u16) } +} +#[cfg(target_has_reliable_f16)] +impl Lemire for f16 { 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() + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ } } -impl RawFloat for f32 { +impl Float for f32 { type Int = u32; const INFINITY: Self = f32::INFINITY; @@ -279,17 +292,23 @@ impl RawFloat for f32 { const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10; const SMALLEST_POWER_OF_TEN: i32 = -65; - #[inline] - fn from_u64(v: u64) -> Self { - debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); - v as _ + fn to_bits(self) -> Self::Int { + self.to_bits() } + fn classify(self) -> FpCategory { + self.classify() + } +} + +impl FloatExt for f32 { #[inline] fn from_u64_bits(v: u64) -> Self { f32::from_bits((v & 0xFFFFFFFF) as u32) } +} +impl Lemire for f32 { fn pow10_fast_path(exponent: usize) -> Self { #[allow(clippy::use_self)] const TABLE: [f32; 16] = @@ -297,16 +316,14 @@ impl RawFloat for f32 { TABLE[exponent & 15] } - fn to_bits(self) -> Self::Int { - self.to_bits() - } - - fn classify(self) -> FpCategory { - self.classify() + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ } } -impl RawFloat for f64 { +impl Float for f64 { type Int = u64; const INFINITY: Self = Self::INFINITY; @@ -323,17 +340,23 @@ impl RawFloat for f64 { const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23; const SMALLEST_POWER_OF_TEN: i32 = -342; - #[inline] - fn from_u64(v: u64) -> Self { - debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); - v as _ + fn to_bits(self) -> Self::Int { + self.to_bits() } + fn classify(self) -> FpCategory { + self.classify() + } +} + +impl FloatExt for f64 { #[inline] fn from_u64_bits(v: u64) -> Self { f64::from_bits(v) } +} +impl Lemire for f64 { fn pow10_fast_path(exponent: usize) -> Self { const TABLE: [f64; 32] = [ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, @@ -342,11 +365,9 @@ impl RawFloat for f64 { TABLE[exponent & 31] } - fn to_bits(self) -> Self::Int { - self.to_bits() - } - - fn classify(self) -> FpCategory { - self.classify() + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ } } diff --git a/library/core/src/num/imp/dec2flt/lemire.rs b/library/core/src/num/imp/dec2flt/lemire.rs index c3f2723509d05..8a97618cf7385 100644 --- a/library/core/src/num/imp/dec2flt/lemire.rs +++ b/library/core/src/num/imp/dec2flt/lemire.rs @@ -1,7 +1,7 @@ //! Implementation of the Eisel-Lemire algorithm. use dec2flt::common::BiasedFp; -use dec2flt::float::RawFloat; +use dec2flt::float::Float; use dec2flt::table::{LARGEST_POWER_OF_FIVE, POWER_OF_FIVE_128, SMALLEST_POWER_OF_FIVE}; use crate::num::imp::dec2flt; @@ -24,7 +24,7 @@ use crate::num::imp::dec2flt; /// at a Gigabyte per Second" in section 5, "Fast Algorithm", and /// section 6, "Exact Numbers And Ties", available online: /// . -pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { +pub fn compute_float(q: i64, mut w: u64) -> BiasedFp { let fp_zero = BiasedFp::zero_pow2(0); let fp_inf = BiasedFp::zero_pow2(F::INFINITE_POWER); let fp_error = BiasedFp::zero_pow2(-1); diff --git a/library/core/src/num/imp/dec2flt/mod.rs b/library/core/src/num/imp/dec2flt/mod.rs index 3f5724add62bb..33e606694bc3e 100644 --- a/library/core/src/num/imp/dec2flt/mod.rs +++ b/library/core/src/num/imp/dec2flt/mod.rs @@ -88,7 +88,7 @@ )] use common::BiasedFp; -use float::RawFloat; +use float::{FloatExt, Lemire}; use lemire::compute_float; use parse::{parse_inf_nan, parse_number}; use slow::parse_long_mantissa; @@ -120,7 +120,7 @@ pub fn pfe_invalid() -> ParseFloatError { } /// Converts a `BiasedFp` to the closest machine float type. -fn biased_fp_to_float(x: BiasedFp) -> F { +fn biased_fp_to_float(x: BiasedFp) -> F { let mut word = x.m; word |= (x.p_biased as u64) << F::SIG_BITS; F::from_u64_bits(word) @@ -128,7 +128,7 @@ fn biased_fp_to_float(x: BiasedFp) -> F { /// Converts a decimal string into a floating point number. #[inline(always)] // Will be inlined into a function with `#[inline(never)]`, see above -pub fn dec2flt(s: &str) -> Result { +pub fn dec2flt(s: &str) -> Result { let mut s = s.as_bytes(); let Some(&c) = s.first() else { return Err(pfe_empty()) }; let negative = c == b'-'; diff --git a/library/core/src/num/imp/dec2flt/parse.rs b/library/core/src/num/imp/dec2flt/parse.rs index e4049bc164c61..e4e3e5bc0c0e3 100644 --- a/library/core/src/num/imp/dec2flt/parse.rs +++ b/library/core/src/num/imp/dec2flt/parse.rs @@ -2,7 +2,7 @@ use dec2flt::common::{ByteSlice, is_8digits}; use dec2flt::decimal::Decimal; -use dec2flt::float::RawFloat; +use dec2flt::float::Float; use crate::num::imp::dec2flt; @@ -197,7 +197,7 @@ pub fn parse_number(s: &[u8]) -> Option { } /// Try to parse a special, non-finite float. -pub(crate) fn parse_inf_nan(s: &[u8], negative: bool) -> Option { +pub(crate) fn parse_inf_nan(s: &[u8], negative: bool) -> Option { // Since a valid string has at most the length 8, we can load // all relevant characters into a u64 and work from there. // This also generates much better code. diff --git a/library/core/src/num/imp/dec2flt/slow.rs b/library/core/src/num/imp/dec2flt/slow.rs index 089b12f5be22e..784f3e00f5b48 100644 --- a/library/core/src/num/imp/dec2flt/slow.rs +++ b/library/core/src/num/imp/dec2flt/slow.rs @@ -2,7 +2,7 @@ use dec2flt::common::BiasedFp; use dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq}; -use dec2flt::float::RawFloat; +use dec2flt::float::Float; use crate::num::imp::dec2flt; @@ -25,7 +25,7 @@ use crate::num::imp::dec2flt; /// /// The algorithms described here are based on "Processing Long Numbers Quickly", /// available here: . -pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { +pub(crate) fn parse_long_mantissa(s: &[u8]) -> BiasedFp { const MAX_SHIFT: usize = 60; const NUM_POWERS: usize = 19; const POWERS: [u8; 19] = diff --git a/library/core/src/num/imp/flt2dec/decoder.rs b/library/core/src/num/imp/flt2dec/decoder.rs index 3d6ad6608efe3..6f8fb2b954f3a 100644 --- a/library/core/src/num/imp/flt2dec/decoder.rs +++ b/library/core/src/num/imp/flt2dec/decoder.rs @@ -1,7 +1,7 @@ //! Decodes a floating-point value into individual parts and error ranges. use crate::num::FpCategory; -use crate::num::imp::dec2flt::float::RawFloat; +use crate::num::imp::dec2flt::float::FloatExt; /// Decoded unsigned finite value, such that: /// @@ -40,7 +40,7 @@ pub enum FullDecoded { } /// A floating point type which can be `decode`d. -pub trait DecodableFloat: RawFloat + Copy { +pub trait DecodableFloat: FloatExt + Copy { /// The minimum positive normalized value. fn min_pos_norm_value() -> Self; } diff --git a/library/coretests/tests/num/dec2flt/float.rs b/library/coretests/tests/num/dec2flt/float.rs index 25e12435b4728..aa4c59dd66ea0 100644 --- a/library/coretests/tests/num/dec2flt/float.rs +++ b/library/coretests/tests/num/dec2flt/float.rs @@ -1,4 +1,4 @@ -use core::num::imp::dec2flt::float::RawFloat; +use core::num::imp::dec2flt::float::{Float, FloatExt, Lemire}; use crate::num::{ldexp_f32, ldexp_f64}; @@ -56,57 +56,60 @@ fn test_f64_integer_decode() { #[test] #[cfg(target_has_reliable_f16)] fn test_f16_consts() { - assert_eq!(::INFINITY, f16::INFINITY); - assert_eq!(::NEG_INFINITY, -f16::INFINITY); - assert_eq!(::NAN.to_bits(), f16::NAN.to_bits()); - assert_eq!(::NEG_NAN.to_bits(), (-f16::NAN).to_bits()); - assert_eq!(::SIG_BITS, 10); - assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -22); - assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 5); - assert_eq!(::MIN_EXPONENT_FAST_PATH, -4); - assert_eq!(::MAX_EXPONENT_FAST_PATH, 4); - assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 7); - assert_eq!(::EXP_MIN, -14); - assert_eq!(::EXP_SAT, 0x1f); - assert_eq!(::SMALLEST_POWER_OF_TEN, -27); - assert_eq!(::LARGEST_POWER_OF_TEN, 4); - assert_eq!(::MAX_MANTISSA_FAST_PATH, 2048); + assert_eq!(::INFINITY, f16::INFINITY); + assert_eq!(::NEG_INFINITY, -f16::INFINITY); + assert_eq!(::NAN.to_bits(), f16::NAN.to_bits()); + assert_eq!(::NEG_NAN.to_bits(), (-f16::NAN).to_bits()); + assert_eq!(::SIG_BITS, 10); + assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -22); + assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 5); + assert_eq!(::EXP_MIN, -14); + assert_eq!(::EXP_SAT, 0x1f); + assert_eq!(::SMALLEST_POWER_OF_TEN, -27); + assert_eq!(::LARGEST_POWER_OF_TEN, 4); + + assert_eq!(::MIN_EXPONENT_FAST_PATH, -4); + assert_eq!(::MAX_EXPONENT_FAST_PATH, 4); + assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 7); + assert_eq!(::MAX_MANTISSA_FAST_PATH, 2048); } #[test] fn test_f32_consts() { - assert_eq!(::INFINITY, f32::INFINITY); - assert_eq!(::NEG_INFINITY, -f32::INFINITY); - assert_eq!(::NAN.to_bits(), f32::NAN.to_bits()); - assert_eq!(::NEG_NAN.to_bits(), (-f32::NAN).to_bits()); - assert_eq!(::SIG_BITS, 23); - assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -17); - assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 10); - assert_eq!(::MIN_EXPONENT_FAST_PATH, -10); - assert_eq!(::MAX_EXPONENT_FAST_PATH, 10); - assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 17); - assert_eq!(::EXP_MIN, -126); - assert_eq!(::EXP_SAT, 0xff); - assert_eq!(::SMALLEST_POWER_OF_TEN, -65); - assert_eq!(::LARGEST_POWER_OF_TEN, 38); - assert_eq!(::MAX_MANTISSA_FAST_PATH, 16777216); + assert_eq!(::INFINITY, f32::INFINITY); + assert_eq!(::NEG_INFINITY, -f32::INFINITY); + assert_eq!(::NAN.to_bits(), f32::NAN.to_bits()); + assert_eq!(::NEG_NAN.to_bits(), (-f32::NAN).to_bits()); + assert_eq!(::SIG_BITS, 23); + assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -17); + assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 10); + assert_eq!(::EXP_MIN, -126); + assert_eq!(::EXP_SAT, 0xff); + assert_eq!(::SMALLEST_POWER_OF_TEN, -65); + assert_eq!(::LARGEST_POWER_OF_TEN, 38); + + assert_eq!(::MIN_EXPONENT_FAST_PATH, -10); + assert_eq!(::MAX_EXPONENT_FAST_PATH, 10); + assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 17); + assert_eq!(::MAX_MANTISSA_FAST_PATH, 16777216); } #[test] fn test_f64_consts() { - assert_eq!(::INFINITY, f64::INFINITY); - assert_eq!(::NEG_INFINITY, -f64::INFINITY); - assert_eq!(::NAN.to_bits(), f64::NAN.to_bits()); - assert_eq!(::NEG_NAN.to_bits(), (-f64::NAN).to_bits()); - assert_eq!(::SIG_BITS, 52); - assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -4); - assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 23); - assert_eq!(::MIN_EXPONENT_FAST_PATH, -22); - assert_eq!(::MAX_EXPONENT_FAST_PATH, 22); - assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 37); - assert_eq!(::EXP_MIN, -1022); - assert_eq!(::EXP_SAT, 0x7ff); - assert_eq!(::SMALLEST_POWER_OF_TEN, -342); - assert_eq!(::LARGEST_POWER_OF_TEN, 308); - assert_eq!(::MAX_MANTISSA_FAST_PATH, 9007199254740992); + assert_eq!(::INFINITY, f64::INFINITY); + assert_eq!(::NEG_INFINITY, -f64::INFINITY); + assert_eq!(::NAN.to_bits(), f64::NAN.to_bits()); + assert_eq!(::NEG_NAN.to_bits(), (-f64::NAN).to_bits()); + assert_eq!(::SIG_BITS, 52); + assert_eq!(::MIN_EXPONENT_ROUND_TO_EVEN, -4); + assert_eq!(::MAX_EXPONENT_ROUND_TO_EVEN, 23); + assert_eq!(::EXP_MIN, -1022); + assert_eq!(::EXP_SAT, 0x7ff); + assert_eq!(::SMALLEST_POWER_OF_TEN, -342); + assert_eq!(::LARGEST_POWER_OF_TEN, 308); + + assert_eq!(::MIN_EXPONENT_FAST_PATH, -22); + assert_eq!(::MAX_EXPONENT_FAST_PATH, 22); + assert_eq!(::MAX_EXPONENT_DISGUISED_FAST_PATH, 37); + assert_eq!(::MAX_MANTISSA_FAST_PATH, 9007199254740992); } diff --git a/library/coretests/tests/num/dec2flt/lemire.rs b/library/coretests/tests/num/dec2flt/lemire.rs index e4ce533ba44da..6679dca148aa6 100644 --- a/library/coretests/tests/num/dec2flt/lemire.rs +++ b/library/coretests/tests/num/dec2flt/lemire.rs @@ -1,6 +1,6 @@ use core::num::imp::dec2flt; -use dec2flt::float::RawFloat; +use dec2flt::float::Float; use dec2flt::lemire::compute_float; #[cfg(target_has_reliable_f16)] From bec94a33d541667abb706e8bab4dc791e64e87cc Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 31 Jan 2026 03:13:08 -0600 Subject: [PATCH 3/3] dec2flt: Move internal traits to better locations `Float` and `FloatExt` are already used by both parsing and printing, so move them out of `dec2flt` to a new module in `num::imp`. `Int` `Cast` have the potential to be used more places in the future, so move them there as well. `Lemire` is the only remaining trait; since it is small, move it into the `dec2flt` root. The `fmt::LowerExp` bound is removed from `Float` here since the trait is moving into a module without `#[cfg(not(no_fp_fmt_parse))]` and it isn't implemented with that config (it's not easily possible to add `cfg` attributes to a single supertrait, unfortunately). This isn't a problem since it isn't actually being used. --- library/core/src/num/imp/dec2flt/decimal.rs | 5 +- library/core/src/num/imp/dec2flt/lemire.rs | 3 +- library/core/src/num/imp/dec2flt/mod.rs | 90 ++++++++++++++- library/core/src/num/imp/dec2flt/parse.rs | 3 +- library/core/src/num/imp/dec2flt/slow.rs | 3 +- library/core/src/num/imp/flt2dec/decoder.rs | 2 +- library/core/src/num/imp/mod.rs | 3 + .../num/imp/{dec2flt/float.rs => traits.rs} | 106 +++--------------- library/coretests/tests/lib.rs | 1 + library/coretests/tests/num/dec2flt/float.rs | 3 +- library/coretests/tests/num/dec2flt/lemire.rs | 3 +- 11 files changed, 113 insertions(+), 109 deletions(-) rename library/core/src/num/imp/{dec2flt/float.rs => traits.rs} (74%) diff --git a/library/core/src/num/imp/dec2flt/decimal.rs b/library/core/src/num/imp/dec2flt/decimal.rs index 583f6bcf3302c..c9b0cc531b386 100644 --- a/library/core/src/num/imp/dec2flt/decimal.rs +++ b/library/core/src/num/imp/dec2flt/decimal.rs @@ -1,9 +1,10 @@ //! Representation of a float as the significant digits and exponent. -use crate::num::imp::dec2flt; -use dec2flt::float::Lemire; +use dec2flt::Lemire; use dec2flt::fpu::set_precision; +use crate::num::imp::dec2flt; + const INT_POW10: [u64; 16] = [ 1, 10, diff --git a/library/core/src/num/imp/dec2flt/lemire.rs b/library/core/src/num/imp/dec2flt/lemire.rs index 8a97618cf7385..f89d16c843477 100644 --- a/library/core/src/num/imp/dec2flt/lemire.rs +++ b/library/core/src/num/imp/dec2flt/lemire.rs @@ -1,10 +1,9 @@ //! Implementation of the Eisel-Lemire algorithm. use dec2flt::common::BiasedFp; -use dec2flt::float::Float; use dec2flt::table::{LARGEST_POWER_OF_FIVE, POWER_OF_FIVE_128, SMALLEST_POWER_OF_FIVE}; -use crate::num::imp::dec2flt; +use crate::num::imp::{Float, dec2flt}; /// Compute w * 10^q using an extended-precision float representation. /// diff --git a/library/core/src/num/imp/dec2flt/mod.rs b/library/core/src/num/imp/dec2flt/mod.rs index 33e606694bc3e..76b8d416ee0f1 100644 --- a/library/core/src/num/imp/dec2flt/mod.rs +++ b/library/core/src/num/imp/dec2flt/mod.rs @@ -88,24 +88,104 @@ )] use common::BiasedFp; -use float::{FloatExt, Lemire}; use lemire::compute_float; use parse::{parse_inf_nan, parse_number}; use slow::parse_long_mantissa; +use crate::f64; use crate::num::ParseFloatError; use crate::num::float_parse::FloatErrorKind; +use crate::num::imp::FloatExt; mod common; pub mod decimal; pub mod decimal_seq; mod fpu; -mod slow; -mod table; -// float is used in flt2dec, and all are used in unit tests. -pub mod float; pub mod lemire; pub mod parse; +mod slow; +mod table; + +/// Extension to `Float` that are necessary for parsing using the Lemire method. +/// +/// See the parent module's doc comment for why this is necessary. +/// +/// Not intended for use outside of the `dec2flt` module. +#[doc(hidden)] +pub trait Lemire: FloatExt { + /// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋` + // assuming FLT_EVAL_METHOD = 0 + const MAX_EXPONENT_FAST_PATH: i64 = { + let log2_5 = f64::consts::LOG2_10 - 1.0; + (Self::SIG_TOTAL_BITS as f64 / log2_5) as i64 + }; + + /// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋` + const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH; + + /// Maximum exponent that can be represented for a disguised-fast path case. + /// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋` + const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = + Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64; + + /// Maximum mantissa for the fast-path (`1 << 53` for f64). + const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS; + + /// Gets a small power-of-ten for fast-path multiplication. + fn pow10_fast_path(exponent: usize) -> Self; + + /// Converts integer into float through an as cast. + /// This is only called in the fast-path algorithm, and therefore + /// will not lose precision, since the value will always have + /// only if the value is <= Self::MAX_MANTISSA_FAST_PATH. + fn from_u64(v: u64) -> Self; +} + +#[cfg(target_has_reliable_f16)] +impl Lemire for f16 { + 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] + } + + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ + } +} + +impl Lemire for f32 { + fn pow10_fast_path(exponent: usize) -> Self { + #[allow(clippy::use_self)] + const TABLE: [f32; 16] = + [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 0., 0., 0., 0., 0.]; + TABLE[exponent & 15] + } + + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ + } +} + +impl Lemire for f64 { + fn pow10_fast_path(exponent: usize) -> Self { + const TABLE: [f64; 32] = [ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, + 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 0., 0., 0., 0., 0., 0., 0., 0., 0., + ]; + TABLE[exponent & 31] + } + + #[inline] + fn from_u64(v: u64) -> Self { + debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); + v as _ + } +} #[inline] pub(super) fn pfe_empty() -> ParseFloatError { diff --git a/library/core/src/num/imp/dec2flt/parse.rs b/library/core/src/num/imp/dec2flt/parse.rs index e4e3e5bc0c0e3..ee55eadbc7b9e 100644 --- a/library/core/src/num/imp/dec2flt/parse.rs +++ b/library/core/src/num/imp/dec2flt/parse.rs @@ -2,9 +2,8 @@ use dec2flt::common::{ByteSlice, is_8digits}; use dec2flt::decimal::Decimal; -use dec2flt::float::Float; -use crate::num::imp::dec2flt; +use crate::num::imp::{Float, dec2flt}; const MIN_19DIGIT_INT: u64 = 100_0000_0000_0000_0000; diff --git a/library/core/src/num/imp/dec2flt/slow.rs b/library/core/src/num/imp/dec2flt/slow.rs index 784f3e00f5b48..f1b2525cf38e6 100644 --- a/library/core/src/num/imp/dec2flt/slow.rs +++ b/library/core/src/num/imp/dec2flt/slow.rs @@ -2,9 +2,8 @@ use dec2flt::common::BiasedFp; use dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq}; -use dec2flt::float::Float; -use crate::num::imp::dec2flt; +use crate::num::imp::{Float, dec2flt}; /// Parse the significant digits and biased, binary exponent of a float. /// diff --git a/library/core/src/num/imp/flt2dec/decoder.rs b/library/core/src/num/imp/flt2dec/decoder.rs index 6f8fb2b954f3a..5ccc91f4c9faf 100644 --- a/library/core/src/num/imp/flt2dec/decoder.rs +++ b/library/core/src/num/imp/flt2dec/decoder.rs @@ -1,7 +1,7 @@ //! Decodes a floating-point value into individual parts and error ranges. use crate::num::FpCategory; -use crate::num::imp::dec2flt::float::FloatExt; +use crate::num::imp::FloatExt; /// Decoded unsigned finite value, such that: /// diff --git a/library/core/src/num/imp/mod.rs b/library/core/src/num/imp/mod.rs index d35409f91bde9..6fccfd1c238ed 100644 --- a/library/core/src/num/imp/mod.rs +++ b/library/core/src/num/imp/mod.rs @@ -16,3 +16,6 @@ pub(crate) mod int_log10; pub(crate) mod int_sqrt; pub(crate) mod libm; pub(crate) mod overflow_panic; +mod traits; + +pub use traits::{Float, FloatExt, Int}; diff --git a/library/core/src/num/imp/dec2flt/float.rs b/library/core/src/num/imp/traits.rs similarity index 74% rename from library/core/src/num/imp/dec2flt/float.rs rename to library/core/src/num/imp/traits.rs index da581a457f495..7b84f7a4a5aa2 100644 --- a/library/core/src/num/imp/dec2flt/float.rs +++ b/library/core/src/num/imp/traits.rs @@ -1,10 +1,14 @@ -//! Helper trait for generic float types. +//! Numeric traits used for internal implementations. -use core::f64; +#![doc(hidden)] +#![unstable( + feature = "num_internals", + reason = "internal routines only exposed for testing", + issue = "none" +)] -use crate::fmt::{Debug, LowerExp}; use crate::num::FpCategory; -use crate::ops::{self, Add, Div, Mul, Neg}; +use crate::{f64, fmt, ops}; /// Lossy `as` casting between two types. pub trait CastInto: Copy { @@ -16,7 +20,7 @@ pub trait Int: Sized + Clone + Copy - + Debug + + fmt::Debug + ops::Shr + ops::Shl + ops::BitAnd @@ -51,17 +55,16 @@ int!(u16, u32, u64); #[doc(hidden)] pub trait Float: Sized - + Div - + Neg - + Mul - + Add - + LowerExp + + ops::Div + + ops::Neg + + ops::Mul + + ops::Add + + fmt::Debug + PartialEq + PartialOrd + Default + Clone + Copy - + Debug { /// The unsigned integer with the same size as the float type Int: Int + Into; @@ -184,41 +187,6 @@ pub trait FloatExt: Float { } } -/// Extension to `Float` that are necessary for parsing using the Lemire method. -/// -/// See the parent module's doc comment for why this is necessary. -/// -/// Not intended for use outside of the `dec2flt` module. -#[doc(hidden)] -pub trait Lemire: FloatExt { - /// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋` - // assuming FLT_EVAL_METHOD = 0 - const MAX_EXPONENT_FAST_PATH: i64 = { - let log2_5 = f64::consts::LOG2_10 - 1.0; - (Self::SIG_TOTAL_BITS as f64 / log2_5) as i64 - }; - - /// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋` - const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH; - - /// Maximum exponent that can be represented for a disguised-fast path case. - /// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋` - const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = - Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64; - - /// Maximum mantissa for the fast-path (`1 << 53` for f64). - const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS; - - /// Gets a small power-of-ten for fast-path multiplication. - fn pow10_fast_path(exponent: usize) -> Self; - - /// Converts integer into float through an as cast. - /// This is only called in the fast-path algorithm, and therefore - /// will not lose precision, since the value will always have - /// only if the value is <= Self::MAX_MANTISSA_FAST_PATH. - fn from_u64(v: u64) -> Self; -} - /// Solve for `b` in `10^b = 2^a` const fn pow2_to_pow10(a: i64) -> i64 { let res = (a as f64) / f64::consts::LOG2_10; @@ -260,21 +228,6 @@ impl FloatExt for f16 { } } -#[cfg(target_has_reliable_f16)] -impl Lemire for f16 { - 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] - } - - #[inline] - fn from_u64(v: u64) -> Self { - debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); - v as _ - } -} - impl Float for f32 { type Int = u32; @@ -308,21 +261,6 @@ impl FloatExt for f32 { } } -impl Lemire for f32 { - fn pow10_fast_path(exponent: usize) -> Self { - #[allow(clippy::use_self)] - const TABLE: [f32; 16] = - [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 0., 0., 0., 0., 0.]; - TABLE[exponent & 15] - } - - #[inline] - fn from_u64(v: u64) -> Self { - debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); - v as _ - } -} - impl Float for f64 { type Int = u64; @@ -355,19 +293,3 @@ impl FloatExt for f64 { f64::from_bits(v) } } - -impl Lemire for f64 { - fn pow10_fast_path(exponent: usize) -> Self { - const TABLE: [f64; 32] = [ - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, - 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 0., 0., 0., 0., 0., 0., 0., 0., 0., - ]; - TABLE[exponent & 31] - } - - #[inline] - fn from_u64(v: u64) -> Self { - debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH); - v as _ - } -} diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 72112f8b01133..b3fd53cbb9dce 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -88,6 +88,7 @@ #![feature(next_index)] #![feature(non_exhaustive_omitted_patterns_lint)] #![feature(nonzero_from_str_radix)] +#![feature(num_internals)] #![feature(numfmt)] #![feature(one_sided_range)] #![feature(panic_internals)] diff --git a/library/coretests/tests/num/dec2flt/float.rs b/library/coretests/tests/num/dec2flt/float.rs index aa4c59dd66ea0..0713c5c651fb3 100644 --- a/library/coretests/tests/num/dec2flt/float.rs +++ b/library/coretests/tests/num/dec2flt/float.rs @@ -1,4 +1,5 @@ -use core::num::imp::dec2flt::float::{Float, FloatExt, Lemire}; +use core::num::imp::dec2flt::Lemire; +use core::num::imp::{Float, FloatExt}; use crate::num::{ldexp_f32, ldexp_f64}; diff --git a/library/coretests/tests/num/dec2flt/lemire.rs b/library/coretests/tests/num/dec2flt/lemire.rs index 6679dca148aa6..e5a7ae346f425 100644 --- a/library/coretests/tests/num/dec2flt/lemire.rs +++ b/library/coretests/tests/num/dec2flt/lemire.rs @@ -1,6 +1,5 @@ -use core::num::imp::dec2flt; +use core::num::imp::{Float, dec2flt}; -use dec2flt::float::Float; use dec2flt::lemire::compute_float; #[cfg(target_has_reliable_f16)]