Skip to main content

core/num/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
90#[cfg(not(feature = "ferrocene_subset"))]
91use self::common::BiasedFp;
92#[cfg(not(feature = "ferrocene_subset"))]
93use self::float::RawFloat;
94#[cfg(not(feature = "ferrocene_subset"))]
95use self::lemire::compute_float;
96#[cfg(not(feature = "ferrocene_subset"))]
97use self::parse::{parse_inf_nan, parse_number};
98#[cfg(not(feature = "ferrocene_subset"))]
99use self::slow::parse_long_mantissa;
100#[cfg(not(feature = "ferrocene_subset"))]
101use crate::error::Error;
102#[cfg(not(feature = "ferrocene_subset"))]
103use crate::fmt;
104#[cfg(not(feature = "ferrocene_subset"))]
105use crate::str::FromStr;
106
107#[cfg(not(feature = "ferrocene_subset"))]
108mod common;
109#[cfg(not(feature = "ferrocene_subset"))]
110pub mod decimal;
111#[cfg(not(feature = "ferrocene_subset"))]
112pub mod decimal_seq;
113#[cfg(not(feature = "ferrocene_subset"))]
114mod fpu;
115#[cfg(not(feature = "ferrocene_subset"))]
116mod slow;
117#[cfg(not(feature = "ferrocene_subset"))]
118mod table;
119// float is used in flt2dec, and all are used in unit tests.
120pub mod float;
121#[cfg(not(feature = "ferrocene_subset"))]
122pub mod lemire;
123#[cfg(not(feature = "ferrocene_subset"))]
124pub mod parse;
125
126#[cfg(not(feature = "ferrocene_subset"))]
127macro_rules! from_str_float_impl {
128    ($t:ty) => {
129        #[stable(feature = "rust1", since = "1.0.0")]
130        impl FromStr for $t {
131            type Err = ParseFloatError;
132
133            /// Converts a string in base 10 to a float.
134            /// Accepts an optional decimal exponent.
135            ///
136            /// This function accepts strings such as
137            ///
138            /// * '3.14'
139            /// * '-3.14'
140            /// * '2.5E10', or equivalently, '2.5e10'
141            /// * '2.5E-10'
142            /// * '5.'
143            /// * '.5', or, equivalently, '0.5'
144            /// * '7'
145            /// * '007'
146            /// * 'inf', '-inf', '+infinity', 'NaN'
147            ///
148            /// Note that alphabetical characters are not case-sensitive.
149            ///
150            /// Leading and trailing whitespace represent an error.
151            ///
152            /// # Grammar
153            ///
154            /// All strings that adhere to the following [EBNF] grammar when
155            /// lowercased will result in an [`Ok`] being returned:
156            ///
157            /// ```txt
158            /// Float  ::= Sign? ( 'inf' | 'infinity' | 'nan' | Number )
159            /// Number ::= ( Digit+ |
160            ///              Digit+ '.' Digit* |
161            ///              Digit* '.' Digit+ ) Exp?
162            /// Exp    ::= 'e' Sign? Digit+
163            /// Sign   ::= [+-]
164            /// Digit  ::= [0-9]
165            /// ```
166            ///
167            /// [EBNF]: https://www.w3.org/TR/REC-xml/#sec-notation
168            ///
169            /// # Arguments
170            ///
171            /// * src - A string
172            ///
173            /// # Return value
174            ///
175            /// `Err(ParseFloatError)` if the string did not represent a valid
176            /// number. Otherwise, `Ok(n)` where `n` is the closest
177            /// representable floating-point number to the number represented
178            /// by `src` (following the same rules for rounding as for the
179            /// results of primitive operations).
180            // We add the `#[inline(never)]` attribute, since its content will
181            // be filled with that of `dec2flt`, which has #[inline(always)].
182            // Since `dec2flt` is generic, a normal inline attribute on this function
183            // with `dec2flt` having no attributes results in heavily repeated
184            // generation of `dec2flt`, despite the fact only a maximum of 2
185            // possible instances can ever exist. Adding #[inline(never)] avoids this.
186            #[inline(never)]
187            fn from_str(src: &str) -> Result<Self, ParseFloatError> {
188                dec2flt(src)
189            }
190        }
191    };
192}
193
194#[cfg(target_has_reliable_f16)]
195#[cfg(not(feature = "ferrocene_subset"))]
196from_str_float_impl!(f16);
197#[cfg(not(feature = "ferrocene_subset"))]
198from_str_float_impl!(f32);
199#[cfg(not(feature = "ferrocene_subset"))]
200from_str_float_impl!(f64);
201
202// FIXME(f16): A fallback is used when the backend+target does not support f16 well, in order
203// to avoid ICEs.
204
205#[cfg(not(target_has_reliable_f16))]
206#[cfg(not(feature = "ferrocene_subset"))]
207impl FromStr for f16 {
208    type Err = ParseFloatError;
209
210    #[inline]
211    fn from_str(_src: &str) -> Result<Self, ParseFloatError> {
212        unimplemented!("requires target_has_reliable_f16")
213    }
214}
215
216/// An error which can be returned when parsing a float.
217///
218/// This error is used as the error type for the [`FromStr`] implementation
219/// for [`f32`] and [`f64`].
220///
221/// # Example
222///
223/// ```
224/// use std::str::FromStr;
225///
226/// if let Err(e) = f64::from_str("a.12") {
227///     println!("Failed conversion to f64: {e}");
228/// }
229/// ```
230#[derive(Debug, Clone, PartialEq, Eq)]
231#[stable(feature = "rust1", since = "1.0.0")]
232#[cfg(not(feature = "ferrocene_subset"))]
233pub struct ParseFloatError {
234    kind: FloatErrorKind,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
238#[cfg(not(feature = "ferrocene_subset"))]
239enum FloatErrorKind {
240    Empty,
241    Invalid,
242}
243
244#[stable(feature = "rust1", since = "1.0.0")]
245#[cfg(not(feature = "ferrocene_subset"))]
246impl Error for ParseFloatError {}
247
248#[stable(feature = "rust1", since = "1.0.0")]
249#[cfg(not(feature = "ferrocene_subset"))]
250impl fmt::Display for ParseFloatError {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        match self.kind {
253            FloatErrorKind::Empty => "cannot parse float from empty string",
254            FloatErrorKind::Invalid => "invalid float literal",
255        }
256        .fmt(f)
257    }
258}
259
260#[inline]
261#[cfg(not(feature = "ferrocene_subset"))]
262pub(super) fn pfe_empty() -> ParseFloatError {
263    ParseFloatError { kind: FloatErrorKind::Empty }
264}
265
266// Used in unit tests, keep public.
267// This is much better than making FloatErrorKind and ParseFloatError::kind public.
268#[inline]
269#[cfg(not(feature = "ferrocene_subset"))]
270pub fn pfe_invalid() -> ParseFloatError {
271    ParseFloatError { kind: FloatErrorKind::Invalid }
272}
273
274/// Converts a `BiasedFp` to the closest machine float type.
275#[cfg(not(feature = "ferrocene_subset"))]
276fn biased_fp_to_float<F: RawFloat>(x: BiasedFp) -> F {
277    let mut word = x.m;
278    word |= (x.p_biased as u64) << F::SIG_BITS;
279    F::from_u64_bits(word)
280}
281
282/// Converts a decimal string into a floating point number.
283#[inline(always)] // Will be inlined into a function with `#[inline(never)]`, see above
284#[cfg(not(feature = "ferrocene_subset"))]
285pub fn dec2flt<F: RawFloat>(s: &str) -> Result<F, ParseFloatError> {
286    let mut s = s.as_bytes();
287    let Some(&c) = s.first() else { return Err(pfe_empty()) };
288    let negative = c == b'-';
289    if c == b'-' || c == b'+' {
290        s = &s[1..];
291    }
292    if s.is_empty() {
293        return Err(pfe_invalid());
294    }
295
296    let mut num = match parse_number(s) {
297        Some(r) => r,
298        None if let Some(value) = parse_inf_nan(s, negative) => return Ok(value),
299        None => return Err(pfe_invalid()),
300    };
301    num.negative = negative;
302    if !cfg!(feature = "optimize_for_size") {
303        if let Some(value) = num.try_fast_path::<F>() {
304            return Ok(value);
305        }
306    }
307
308    // If significant digits were truncated, then we can have rounding error
309    // only if `mantissa + 1` produces a different result. We also avoid
310    // redundantly using the Eisel-Lemire algorithm if it was unable to
311    // correctly round on the first pass.
312    let mut fp = compute_float::<F>(num.exponent, num.mantissa);
313    if num.many_digits
314        && fp.p_biased >= 0
315        && fp != compute_float::<F>(num.exponent, num.mantissa + 1)
316    {
317        fp.p_biased = -1;
318    }
319    // Unable to correctly round the float using the Eisel-Lemire algorithm.
320    // Fallback to a slower, but always correct algorithm.
321    if fp.p_biased < 0 {
322        fp = parse_long_mantissa::<F>(s);
323    }
324
325    let mut float = biased_fp_to_float::<F>(fp);
326    if num.negative {
327        float = -float;
328    }
329    Ok(float)
330}