Skip to main content

core/num/imp/dec2flt/
mod.rs

1//! Converting decimal strings into IEEE 754 binary floating point numbers.
2//!
3//! # Problem statement
4//!
5//! We are given a decimal string such as `12.34e56`. This string consists of integral (`12`),
6//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as a
7//! default value (1 or 0) when missing.
8//!
9//! We seek the IEEE 754 floating point number that is closest to the exact value of the decimal
10//! string. It is well-known that many decimal strings do not have terminating representations in
11//! base two, so we round to 0.5 units in the last place (in other words, as well as possible).
12//! Ties, decimal values exactly half-way between two consecutive floats, are resolved with the
13//! half-to-even strategy, also known as banker's rounding.
14//!
15//! Needless to say, this is quite hard, both in terms of implementation complexity and in terms
16//! of CPU cycles taken.
17//!
18//! # Implementation
19//!
20//! First, we ignore signs. Or rather, we remove it at the very beginning of the conversion
21//! process and re-apply it at the very end. This is correct in all edge cases since IEEE
22//! floats are symmetric around zero, negating one simply flips the first bit.
23//!
24//! Then we remove the decimal point by adjusting the exponent: Conceptually, `12.34e56` turns
25//! into `1234e54`, which we describe with a positive integer `f = 1234` and an integer `e = 54`.
26//! The `(f, e)` representation is used by almost all code past the parsing stage.
27//!
28//! We then try a long chain of progressively more general and expensive special cases using
29//! machine-sized integers and small, fixed-sized floating point numbers (first `f32`/`f64`, then
30//! a type with 64 bit significand). The extended-precision algorithm
31//! uses the Eisel-Lemire algorithm, which uses a 128-bit (or 192-bit)
32//! representation that can accurately and quickly compute the vast majority
33//! of floats. When all these fail, we bite the bullet and resort to using
34//! a large-decimal representation, shifting the digits into range, calculating
35//! the upper significant bits and exactly round to the nearest representation.
36//!
37//! Another aspect that needs attention is the ``RawFloat`` trait by which almost all functions
38//! are parametrized. One might think that it's enough to parse to `f64` and cast the result to
39//! `f32`. Unfortunately this is not the world we live in, and this has nothing to do with using
40//! base two or half-to-even rounding.
41//!
42//! Consider for example two types `d2` and `d4` representing a decimal type with two decimal
43//! digits and four decimal digits each and take "0.01499" as input. Let's use half-up rounding.
44//! Going directly to two decimal digits gives `0.01`, but if we round to four digits first,
45//! we get `0.0150`, which is then rounded up to `0.02`. The same principle applies to other
46//! operations as well, if you want 0.5 ULP accuracy you need to do *everything* in full precision
47//! and round *exactly once, at the end*, by considering all truncated bits at once.
48//!
49//! Primarily, this module and its children implement the algorithms described in:
50//! "Number Parsing at a Gigabyte per Second", available online:
51//! <https://arxiv.org/abs/2101.11408>.
52//!
53//! # Other
54//!
55//! The conversion should *never* panic. There are assertions and explicit panics in the code,
56//! but they should never be triggered and only serve as internal sanity checks. Any panics should
57//! be considered a bug.
58//!
59//! There are unit tests but they are woefully inadequate at ensuring correctness, they only cover
60//! a small percentage of possible errors. Far more extensive tests are located in the directory
61//! `src/tools/test-float-parse` as a Rust program.
62//!
63//! A note on integer overflow: Many parts of this file perform arithmetic with the decimal
64//! exponent `e`. Primarily, we shift the decimal point around: Before the first decimal digit,
65//! after the last decimal digit, and so on. This could overflow if done carelessly. We rely on
66//! the parsing submodule to only hand out sufficiently small exponents, where "sufficient" means
67//! "such that the exponent +/- the number of decimal digits fits into a 64 bit integer".
68//! Larger exponents are accepted, but we don't do arithmetic with them, they are immediately
69//! turned into {positive,negative} {zero,infinity}.
70//!
71//! # Notation
72//!
73//! This module uses the same notation as the Lemire paper:
74//!
75//! - `m`: binary mantissa; always nonnegative
76//! - `p`: binary exponent; a signed integer
77//! - `w`: decimal significand; always nonnegative
78//! - `q`: decimal exponent; a signed integer
79//!
80//! This gives `m * 2^p` for the binary floating-point number, with `w * 10^q` as the decimal
81//! equivalent.
82
83#![doc(hidden)]
84#![unstable(
85    feature = "dec2flt",
86    reason = "internal routines only exposed for testing",
87    issue = "none"
88)]
89
90use common::BiasedFp;
91use lemire::compute_float;
92use parse::{parse_inf_nan, parse_number};
93use slow::parse_long_mantissa;
94
95use crate::f64;
96use crate::num::imp::FloatExt;
97use crate::num::{FloatErrorKind, ParseFloatError};
98
99mod common;
100pub mod decimal;
101pub mod decimal_seq;
102mod fpu;
103pub mod lemire;
104pub mod parse;
105mod slow;
106mod table;
107
108/// Extension to `Float` that are necessary for parsing using the Lemire method.
109///
110/// See the parent module's doc comment for why this is necessary.
111///
112/// Not intended for use outside of the `dec2flt` module.
113#[doc(hidden)]
114pub trait Lemire: FloatExt {
115    /// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋`
116    // assuming FLT_EVAL_METHOD = 0
117    const MAX_EXPONENT_FAST_PATH: i64 = {
118        let log2_5 = f64::consts::LOG2_10 - 1.0;
119        (Self::SIG_TOTAL_BITS as f64 / log2_5) as i64
120    };
121
122    /// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋`
123    const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH;
124
125    /// Maximum exponent that can be represented for a disguised-fast path case.
126    /// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋`
127    const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 =
128        Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64;
129
130    /// Maximum mantissa for the fast-path (`1 << 53` for f64).
131    const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS;
132
133    /// Gets a small power-of-ten for fast-path multiplication.
134    fn pow10_fast_path(exponent: usize) -> Self;
135
136    /// Converts integer into float through an as cast.
137    /// This is only called in the fast-path algorithm, and therefore
138    /// will not lose precision, since the value will always have
139    /// only if the value is <= Self::MAX_MANTISSA_FAST_PATH.
140    fn from_u64(v: u64) -> Self;
141}
142
143#[cfg(target_has_reliable_f16)]
144impl Lemire for f16 {
145    fn pow10_fast_path(exponent: usize) -> Self {
146        #[allow(clippy::use_self)]
147        const TABLE: [f16; 8] = [1e0, 1e1, 1e2, 1e3, 1e4, 0.0, 0.0, 0.];
148        TABLE[exponent & 7]
149    }
150
151    #[inline]
152    fn from_u64(v: u64) -> Self {
153        debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH);
154        v as _
155    }
156}
157
158impl Lemire for f32 {
159    fn pow10_fast_path(exponent: usize) -> Self {
160        #[allow(clippy::use_self)]
161        const TABLE: [f32; 16] =
162            [1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 0., 0., 0., 0., 0.];
163        TABLE[exponent & 15]
164    }
165
166    #[inline]
167    fn from_u64(v: u64) -> Self {
168        debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH);
169        v as _
170    }
171}
172
173impl Lemire for f64 {
174    fn pow10_fast_path(exponent: usize) -> Self {
175        const TABLE: [f64; 32] = [
176            1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
177            1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 0., 0., 0., 0., 0., 0., 0., 0., 0.,
178        ];
179        TABLE[exponent & 31]
180    }
181
182    #[inline]
183    fn from_u64(v: u64) -> Self {
184        debug_assert!(v <= Self::MAX_MANTISSA_FAST_PATH);
185        v as _
186    }
187}
188
189#[inline]
190pub(super) fn pfe_empty() -> ParseFloatError {
191    ParseFloatError { kind: FloatErrorKind::Empty }
192}
193
194// Only used in unit tests, keep public.
195#[inline]
196pub fn pfe_invalid() -> ParseFloatError {
197    ParseFloatError { kind: FloatErrorKind::Invalid }
198}
199
200/// Converts a `BiasedFp` to the closest machine float type.
201fn biased_fp_to_float<F: FloatExt>(x: BiasedFp) -> F {
202    let mut word = x.m;
203    word |= (x.p_biased as u64) << F::SIG_BITS;
204    F::from_u64_bits(word)
205}
206
207/// Converts a decimal string into a floating point number.
208#[inline(always)] // Will be inlined into a function with `#[inline(never)]`, see above
209pub fn dec2flt<F: Lemire>(s: &str) -> Result<F, ParseFloatError> {
210    let mut s = s.as_bytes();
211    let Some(&c) = s.first() else { return Err(pfe_empty()) };
212    let negative = c == b'-';
213    if c == b'-' || c == b'+' {
214        s = &s[1..];
215    }
216    if s.is_empty() {
217        return Err(pfe_invalid());
218    }
219
220    let mut num = match parse_number(s) {
221        Some(r) => r,
222        None if let Some(value) = parse_inf_nan(s, negative) => return Ok(value),
223        None => return Err(pfe_invalid()),
224    };
225    num.negative = negative;
226    if !cfg!(feature = "optimize_for_size") {
227        if let Some(value) = num.try_fast_path::<F>() {
228            return Ok(value);
229        }
230    }
231
232    // If significant digits were truncated, then we can have rounding error
233    // only if `mantissa + 1` produces a different result. We also avoid
234    // redundantly using the Eisel-Lemire algorithm if it was unable to
235    // correctly round on the first pass.
236    let mut fp = compute_float::<F>(num.exponent, num.mantissa);
237    if num.many_digits
238        && fp.p_biased >= 0
239        && fp != compute_float::<F>(num.exponent, num.mantissa + 1)
240    {
241        fp.p_biased = -1;
242    }
243    // Unable to correctly round the float using the Eisel-Lemire algorithm.
244    // Fallback to a slower, but always correct algorithm.
245    if fp.p_biased < 0 {
246        fp = parse_long_mantissa::<F>(s);
247    }
248
249    let mut float = biased_fp_to_float::<F>(fp);
250    if num.negative {
251        float = -float;
252    }
253    Ok(float)
254}