Skip to main content

malachite_float/float/conversion/string/
from_sci_string.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8//
9// Malachite's own scientific-string parsing, driven by `FromSciStringOptions`: the counterpart of
10// `to_sci.rs`, and the reverse-direction sibling of `strtofr.rs`.
11//
12// The grammar is the one the rest of Malachite uses, `preprocess_sci_string` from malachite-base,
13// so a `Float` reads a string the same way a `Rational` does. That differs from MPFR's, which
14// `strtofr.rs` implements instead; the two never disagree about a string's value, but each accepts
15// some the other rejects (see PORTING.md). What `Float` adds over `Rational` is the three special
16// values, a signed zero, and a precision and rounding mode, with the ternary value they imply.
17//
18// The digits are handed to the same `set_str_helper` core that `strtofr.rs` uses, so the arithmetic
19// is shared and only the grammar differs.
20
21use crate::Float;
22use crate::float::conversion::string::get_str::ceil_mul;
23use crate::float::conversion::string::set_str::{overflow, set_str_helper};
24use alloc::borrow::Cow;
25use alloc::format;
26use alloc::vec::Vec;
27use core::cmp::Ordering::{self, *};
28use core::str::FromStr;
29use malachite_base::num::basic::traits::{Infinity, NaN, NegativeInfinity, NegativeZero, Zero};
30use malachite_base::num::conversion::string::from_sci_string::preprocess_sci_string;
31use malachite_base::num::conversion::string::from_string::digit_from_display_byte;
32use malachite_base::num::conversion::string::options::FromSciStringOptions;
33use malachite_base::num::conversion::traits::{
34    ExactFrom, FromSciString, IntegerMantissaAndExponent,
35};
36use malachite_base::num::logic::traits::SignificantBits;
37use malachite_base::rounding_modes::RoundingMode::{self, *};
38
39// The outcome of reading a string's digits, before any rounding.
40enum Parsed {
41    // A NaN or an infinity, which carry no precision.
42    Special(Float),
43    // A zero with the given sign.
44    Zero(bool),
45    // The sign, the digit values (most significant first, with leading and trailing zeros
46    // stripped), the number of digits before the point plus any exponent, and how many significant
47    // digits the string had before the trailing zeros were stripped.
48    Finite(bool, Vec<u8>, i64, usize),
49    // The exponent is too large for the value to be finite.
50    Overflow(bool),
51}
52
53// Reads `s` in the base given by `options`, returning `None` if it is not a valid number.
54fn parse(s: &str, options: FromSciStringOptions) -> Option<Parsed> {
55    // The special values, spelled as `Float`'s `Display` writes them.
56    match s {
57        "NaN" => return Some(Parsed::Special(Float::NAN)),
58        "Infinity" => return Some(Parsed::Special(Float::INFINITY)),
59        "-Infinity" => return Some(Parsed::Special(Float::NEGATIVE_INFINITY)),
60        _ => {}
61    }
62    let base = options.get_base();
63    // `preprocess_sci_string` removes the point and folds it, and any exponent, into a power of the
64    // base: the result is the digit characters and an exponent `e` with the value being those
65    // digits, read as an integer, times base ^ e.
66    let (chars, exponent) = preprocess_sci_string(s, options)?;
67    let (sign, chars) = match chars.split_first() {
68        Some((&b'-', rest)) => (false, rest),
69        Some((&b'+', rest)) => (true, rest),
70        _ => (true, &chars[..]),
71    };
72    if chars.is_empty() {
73        return None;
74    }
75    let mut digits = Vec::with_capacity(chars.len());
76    for &c in chars {
77        let digit = digit_from_display_byte(c)?;
78        if digit >= base {
79            return None;
80        }
81        digits.push(digit);
82    }
83    // Restate the value as 0.d1 d2 ... times base ^ exp_base, the form the core takes.
84    let Some(mut exp_base) = i64::exact_from(digits.len()).checked_add(exponent) else {
85        // Only an exponent near the top of its range can do this, and the digit count is positive,
86        // so the sum can only have overflowed upwards.
87        return Some(Parsed::Overflow(sign));
88    };
89    // Leading zeros are not significant and each one lowers the exponent.
90    let leading = digits.iter().take_while(|&&d| d == 0).count();
91    digits.drain(..leading);
92    exp_base -= i64::exact_from(leading);
93    // Every digit that is left counts towards the precision the string implies, including the
94    // trailing zeros the core does not want.
95    let significant = digits.len();
96    while digits.last() == Some(&0) {
97        digits.pop();
98    }
99    Some(if digits.is_empty() {
100        Parsed::Zero(sign)
101    } else {
102        Parsed::Finite(sign, digits, exp_base, significant)
103    })
104}
105
106// The precision a string implies when the caller does not give one: as many bits as its significant
107// digits can carry.
108fn implied_prec(significant: usize, base: u8) -> u64 {
109    u64::exact_from(ceil_mul(i64::exact_from(significant), u64::from(base), 0))
110}
111
112impl Float {
113    /// Converts a string, possibly in scientific notation, to a [`Float`], with a given precision
114    /// and rounding mode.
115    ///
116    /// The string is read in base 10; use
117    /// [`from_sci_string_with_options_prec`](Float::from_sci_string_with_options_prec) for another
118    /// base. The result is the string's exact value rounded once to `prec` bits with `rm`, together
119    /// with the [`Ordering`] of the result against that exact value. `None` means the string is not
120    /// a number.
121    ///
122    /// See [`from_sci_string_with_options_prec`](Float::from_sci_string_with_options_prec) for the
123    /// grammar and for the treatment of the special values and of zero.
124    ///
125    /// # Worst-case complexity
126    /// $T(n) = O(n (\log n)^2 \log\log n)$
127    ///
128    /// $M(n) = O(n \log n)$
129    ///
130    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(s.len(), prec)`.
131    ///
132    /// # Panics
133    /// Panics if `prec` is zero, or if `rm` is `Exact` but the string's value is not exactly
134    /// representable with `prec` bits.
135    ///
136    /// # Examples
137    /// ```
138    /// use core::cmp::Ordering::*;
139    /// use malachite_base::rounding_modes::RoundingMode::*;
140    /// use malachite_float::Float;
141    ///
142    /// let s = |s, prec, rm| {
143    ///     Float::from_sci_string_prec_round(s, prec, rm).map(|(x, o)| (x.to_string(), o))
144    /// };
145    ///
146    /// assert_eq!(s("1.5", 10, Nearest), Some(("1.5000".to_string(), Equal)));
147    ///
148    /// // 0.1 is not representable in binary, so it is rounded and the `Ordering` gives the
149    /// // direction.
150    /// assert_eq!(s("0.1", 4, Floor), Some(("0.0938".to_string(), Less)));
151    /// assert_eq!(s("0.1", 4, Ceiling), Some(("0.102".to_string(), Greater)));
152    ///
153    /// assert_eq!(s("abc", 10, Nearest), None);
154    /// ```
155    pub fn from_sci_string_prec_round(
156        s: &str,
157        prec: u64,
158        rm: RoundingMode,
159    ) -> Option<(Self, Ordering)> {
160        let mut options = FromSciStringOptions::default();
161        options.set_rounding_mode(rm);
162        Self::from_sci_string_with_options_prec(s, options, prec)
163    }
164
165    /// Converts a string, possibly in scientific notation, to a [`Float`], with a given precision,
166    /// rounding to nearest.
167    ///
168    /// This is [`from_sci_string_prec_round`](Float::from_sci_string_prec_round) with `Nearest`.
169    ///
170    /// # Worst-case complexity
171    /// $T(n) = O(n (\log n)^2 \log\log n)$
172    ///
173    /// $M(n) = O(n \log n)$
174    ///
175    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(s.len(), prec)`.
176    ///
177    /// # Panics
178    /// Panics if `prec` is zero.
179    ///
180    /// # Examples
181    /// ```
182    /// use core::cmp::Ordering::*;
183    /// use malachite_float::Float;
184    ///
185    /// let s = |s, prec| Float::from_sci_string_prec(s, prec).map(|(x, o)| (x.to_string(), o));
186    ///
187    /// assert_eq!(s("1.5", 10), Some(("1.5000".to_string(), Equal)));
188    /// assert_eq!(s("0.1", 4), Some(("0.102".to_string(), Greater)));
189    /// assert_eq!(
190    ///     s("1e10", 53),
191    ///     Some(("10000000000.000000".to_string(), Equal))
192    /// );
193    /// assert_eq!(s("abc", 10), None);
194    /// ```
195    #[inline]
196    pub fn from_sci_string_prec(s: &str, prec: u64) -> Option<(Self, Ordering)> {
197        Self::from_sci_string_with_options_prec(s, FromSciStringOptions::default(), prec)
198    }
199
200    /// Converts a string, possibly in scientific notation, to a [`Float`], with a given precision,
201    /// using the given options for the base and the rounding mode.
202    ///
203    /// The result is the string's exact value rounded once to `prec` bits, together with the
204    /// [`Ordering`] of the result against that exact value. `None` means the string is not a
205    /// number; it never means the value is out of range, since a value too large in magnitude gives
206    /// an infinity (or, under a mode that rounds toward zero, the largest finite value) and one too
207    /// small gives a zero.
208    ///
209    /// Use [`FromSciStringOptions`] to specify the base, from 2 to 36 inclusive, and the rounding
210    /// mode. This is the grammar the rest of Malachite uses, so a [`Float`] reads a string the same
211    /// way a [`Rational`](malachite_q::Rational) does, with three additions: the strings `NaN`,
212    /// `Infinity`, and `-Infinity`, which are what [`Float`]'s [`Display`](std::fmt::Display)
213    /// writes and are read in every base; a signed zero, so that `-0.0` is negative zero rather
214    /// than zero; and the precision and rounding mode. Note that from base 24 up `NaN` is also a
215    /// valid digit string, and from base 35 up so is `Infinity`; the special value wins.
216    ///
217    /// If the base is greater than 10, the higher digits are represented by the letters `'a'`
218    /// through `'z'` or `'A'` through `'Z'`; the case doesn't matter and doesn't need to be
219    /// consistent.
220    ///
221    /// Exponents are allowed, and are indicated using the character `'e'` or `'E'`. If the base is
222    /// 15 or greater, an ambiguity arises where it may not be clear whether `'e'` is a digit or an
223    /// exponent indicator. To resolve this ambiguity, always use a `'+'` or `'-'` sign after the
224    /// exponent indicator when the base is 15 or greater. The exponent itself is always parsed
225    /// using base 10.
226    ///
227    /// Points are allowed.
228    ///
229    /// # Worst-case complexity
230    /// $T(n) = O(n (\log n)^2 \log\log n)$
231    ///
232    /// $M(n) = O(n \log n)$
233    ///
234    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(s.len(), prec)`.
235    ///
236    /// # Panics
237    /// Panics if `prec` is zero, or if the rounding mode is `Exact` but the string's value is not
238    /// exactly representable with `prec` bits.
239    ///
240    /// # Examples
241    /// ```
242    /// use core::cmp::Ordering::*;
243    /// use malachite_base::num::conversion::string::options::FromSciStringOptions;
244    /// use malachite_base::rounding_modes::RoundingMode::*;
245    /// use malachite_float::Float;
246    ///
247    /// let s = |s, options, prec| {
248    ///     Float::from_sci_string_with_options_prec(s, options, prec)
249    ///         .map(|(x, o)| (x.to_string(), o))
250    /// };
251    ///
252    /// let mut options = FromSciStringOptions::default();
253    /// options.set_base(16);
254    /// assert_eq!(
255    ///     s("ff", options, 53),
256    ///     Some(("255.00000000000000".to_string(), Equal))
257    /// );
258    /// // From base 15 up, an exponent needs an explicit sign, since `e` is also a digit.
259    /// assert_eq!(
260    ///     s("1e5", options, 20),
261    ///     Some(("485.00000".to_string(), Equal))
262    /// );
263    /// assert_eq!(
264    ///     s("1e+5", options, 20),
265    ///     Some(("1048576.0".to_string(), Equal))
266    /// );
267    ///
268    /// // The rounding mode comes from the options.
269    /// options.set_base(10);
270    /// options.set_rounding_mode(Floor);
271    /// assert_eq!(s("0.1", options, 4), Some(("0.0938".to_string(), Less)));
272    /// options.set_rounding_mode(Ceiling);
273    /// assert_eq!(s("0.1", options, 4), Some(("0.102".to_string(), Greater)));
274    ///
275    /// // Zero keeps its sign, and an exponent too large to represent gives an infinity.
276    /// assert_eq!(s("-0.0", options, 53), Some(("-0.0".to_string(), Equal)));
277    /// assert_eq!(
278    ///     s("1e1000000000000000000", options, 53),
279    ///     Some(("Infinity".to_string(), Greater))
280    /// );
281    /// ```
282    pub fn from_sci_string_with_options_prec(
283        s: &str,
284        options: FromSciStringOptions,
285        prec: u64,
286    ) -> Option<(Self, Ordering)> {
287        assert_ne!(prec, 0);
288        let rm = options.get_rounding_mode();
289        Some(match parse(s, options)? {
290            Parsed::Special(x) => (x, Equal),
291            Parsed::Zero(sign) => (
292                if sign {
293                    Self::ZERO
294                } else {
295                    Self::NEGATIVE_ZERO
296                },
297                Equal,
298            ),
299            Parsed::Overflow(sign) => overflow(sign, prec, rm),
300            Parsed::Finite(sign, digits, exp_base, _) => {
301                set_str_helper(sign, &digits, options.get_base(), exp_base, 0, prec, rm)
302            }
303        })
304    }
305}
306
307// The base prefix `to_string.rs` writes for a power-of-two base, if any.
308const fn base_prefix(base: u8) -> &'static str {
309    match base {
310        2 => "0b",
311        8 => "0o",
312        16 => "0x",
313        _ => "",
314    }
315}
316
317pub_crate_test! {
318// Reads what `ComparableFloat` writes in the given base: an optional sign, an optional base prefix,
319// the digits, and an optional `#` and precision. Without the suffix the precision is inferred from
320// the digits.
321float_from_string_base(base: u8, s: &str) -> Option<Float> {
322    // Built first, so that an invalid base is rejected whatever the string is; otherwise the
323    // strings that fail below would quietly return `None` for a base that cannot exist.
324    let mut options = FromSciStringOptions::default();
325    options.set_base(base);
326    let (body, prec) = match s.rfind('#') {
327        Some(i) => (&s[..i], Some(u64::from_str(&s[i + 1..]).ok()?)),
328        None => (s, None),
329    };
330    // A precision of zero is not a `Float` precision, and the specials and zeros never carry one.
331    if prec == Some(0) {
332        return None;
333    }
334    let (sign, after_sign) = match body.strip_prefix('-') {
335        Some(rest) => ("-", rest),
336        None => ("", body),
337    };
338    // The prefix is optional: `{:x}` omits it and `{:#x}` writes it.
339    let prefix = base_prefix(base);
340    let digits = if prefix.is_empty() {
341        after_sign
342    } else {
343        after_sign.strip_prefix(prefix).unwrap_or(after_sign)
344    };
345    // `from_sci_string` reads the sign itself, and the specials only in their unprefixed spelling.
346    let rebuilt = if sign.is_empty() && prefix.is_empty() {
347        Cow::Borrowed(body)
348    } else {
349        Cow::Owned(format!("{sign}{digits}"))
350    };
351    match prec {
352        Some(prec) => {
353            let x = Float::from_sci_string_with_options_prec(&rebuilt, options, prec)?.0;
354            // A suffix that a special or a zero cannot carry is not something they wrote.
355            x.get_prec().map(|_| x)
356        }
357        None => Float::from_sci_string_with_options(&rebuilt, options),
358    }
359}}
360
361impl FromSciString for Float {
362    /// Converts a string, possibly in scientific notation, to a [`Float`], inferring a precision
363    /// from the number of digits.
364    ///
365    /// The grammar, the base, and the treatment of the special values and of zero are as in
366    /// [`from_sci_string_with_options_prec`](Float::from_sci_string_with_options_prec), which this
367    /// differs from only in where the precision comes from. The rounding mode option is ignored;
368    /// the value is rounded to nearest.
369    ///
370    /// A string does not say how precise it is, so the precision has to be guessed, and the guess
371    /// is that its $n$ significant digits are all meaningful: $\lceil n \log_2 b \rceil$ bits. If
372    /// the value is exactly representable in fewer bits than that, it is stored in the fewest that
373    /// represent it, which makes a literal agree with [`Float::from`]: `"1.5"` gives precision 2
374    /// and `"255"` gives 8, matching `Float::from(1.5)` and `Float::from(255)`.
375    ///
376    /// This is worth dwelling on, because for short strings the guess is coarse in a way that may
377    /// surprise, much as
378    /// [`from_sci_string_simplest`](malachite_q::Rational::from_sci_string_simplest) does for
379    /// [`Rational`](malachite_q::Rational). One decimal digit buys only four bits, so `"0.1"` gives
380    /// a precision-4 [`Float`] whose value is $13/128$, or 0.1015625 — not the nearest `f64` to
381    /// 0.1, and not close to it. Reading `"0.1000000000000000055511151231257827"` gives that
382    /// instead, and asking for a precision outright with
383    /// [`from_sci_string_prec`](Float::from_sci_string_prec) avoids the question altogether. Note
384    /// also that a string with a huge exponent is not thereby precise: `"1e100000000"` still has
385    /// one significant digit, so it gives four bits rather than its exact 332 million.
386    ///
387    /// # Worst-case complexity
388    /// $T(n) = O(n (\log n)^2 \log\log n)$
389    ///
390    /// $M(n) = O(n \log n)$
391    ///
392    /// where $T$ is time, $M$ is additional memory, and $n$ is `s.len()`.
393    ///
394    /// # Examples
395    /// ```
396    /// use malachite_base::num::conversion::string::options::FromSciStringOptions;
397    /// use malachite_base::num::conversion::traits::FromSciString;
398    /// use malachite_float::Float;
399    ///
400    /// // An exactly representable value is stored in the fewest bits that represent it, so these
401    /// // agree with `Float::from`.
402    /// assert_eq!(Float::from_sci_string("1.5").unwrap(), Float::from(1.5));
403    /// assert_eq!(Float::from_sci_string("255").unwrap(), Float::from(255));
404    ///
405    /// // A value that is not exactly representable keeps the precision its digits imply, which
406    /// // for short strings is coarse: one decimal digit buys only four bits.
407    /// assert_eq!(Float::from_sci_string("0.1").unwrap().to_string(), "0.102");
408    /// assert_eq!(
409    ///     Float::from_sci_string("3.14159").unwrap().to_string(),
410    ///     "3.1415901"
411    /// );
412    ///
413    /// // A huge exponent does not make the digits more precise.
414    /// assert_eq!(
415    ///     Float::from_sci_string("1e100000000").unwrap().to_string(),
416    ///     "9.80e99999999"
417    /// );
418    ///
419    /// let mut options = FromSciStringOptions::default();
420    /// options.set_base(16);
421    /// assert_eq!(
422    ///     Float::from_sci_string_with_options("ff", options).unwrap(),
423    ///     Float::from(255)
424    /// );
425    ///
426    /// assert!(Float::from_sci_string("abc").is_none());
427    /// ```
428    fn from_sci_string_with_options(s: &str, options: FromSciStringOptions) -> Option<Self> {
429        let base = options.get_base();
430        Some(match parse(s, options)? {
431            Parsed::Special(x) => x,
432            Parsed::Zero(sign) => {
433                if sign {
434                    Self::ZERO
435                } else {
436                    Self::NEGATIVE_ZERO
437                }
438            }
439            // This is `overflow(sign, _, Nearest)`, which is an infinity whatever the precision.
440            // That is as well, since the digits cannot pin one down: an overflowing exponent says
441            // nothing about how many of them there were.
442            Parsed::Overflow(sign) => {
443                if sign {
444                    Self::INFINITY
445                } else {
446                    Self::NEGATIVE_INFINITY
447                }
448            }
449            Parsed::Finite(sign, digits, exp_base, significant) => {
450                let prec = implied_prec(significant, base);
451                let (x, o) = set_str_helper(sign, &digits, base, exp_base, 0, prec, Nearest);
452                // The implied precision is an upper bound on what the digits can say; when the
453                // value needs fewer bits than that, it is stored with the fewest that represent it,
454                // matching `Float::from` for a primitive float. A rounded value is not shrunk: its
455                // low bits are not the string's.
456                //
457                // An exact result here is never zero, so there is no need to guard the shrink
458                // against one: the digits are nonempty with their trailing zeros stripped, so they
459                // name a value of at least one, and the only way `set_str_helper` reaches zero is
460                // by underflowing, which is never exact.
461                if o == Equal {
462                    let min_prec = (&x).integer_mantissa().significant_bits();
463                    Self::from_float_prec_round(x, min_prec, Exact).0
464                } else {
465                    x
466                }
467            }
468        })
469    }
470}