Skip to main content

malachite_float/float/conversion/string/
strtofr.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MPFR Library.
4//
5//      Copyright © 2004-2024 Free Software Foundation, Inc.
6//
7//      Contributed by the AriC and Caramba projects, INRIA.
8//
9// This file is part of Malachite.
10//
11// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
12// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
13// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
14
15use crate::Float;
16use crate::float::conversion::string::set_str::{overflow, set_str_helper};
17use alloc::vec::Vec;
18use core::cmp::Ordering::{self, *};
19use malachite_base::num::basic::traits::{Infinity, NaN, NegativeInfinity, NegativeZero, Zero};
20use malachite_base::rounding_modes::RoundingMode;
21
22// The largest base `strtofr` accepts.
23//
24// This is `MPFR_MAX_BASE` from `strtofr.c`, MPFR 4.3.0.
25const MAX_BASE: u8 = 62;
26
27// C's `isspace` in the "C" locale. Rust's `is_ascii_whitespace` is not the same: it omits the
28// vertical tab.
29const fn is_space(c: u8) -> bool {
30    matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
31}
32
33// The value of the digit character `c` in base `base`, or `None` if `c` is not a digit of that
34// base. For a base of 36 or less the letter case does not matter; above that, lowercase letters
35// continue the sequence after the uppercase ones.
36//
37// This is `digit_value_in_base` from `strtofr.c`, MPFR 4.3.0.
38const fn digit_value_in_base(c: u8, base: u8) -> Option<u8> {
39    let digit = match c {
40        b'0'..=b'9' => c - b'0',
41        b'a'..=b'z' => {
42            if base >= 37 {
43                c - b'a' + 36
44            } else {
45                c - b'a' + 10
46            }
47        }
48        b'A'..=b'Z' => c - b'A' + 10,
49        _ => return None,
50    };
51    if digit < base { Some(digit) } else { None }
52}
53
54// Whether `s` begins with `prefix`, which must be lowercase, ignoring ASCII case.
55//
56// This is `fast_casecmp` from `strtofr.c`, MPFR 4.3.0, returning whether the comparison succeeded.
57fn starts_with_ignore_case(s: &[u8], prefix: &[u8]) -> bool {
58    let prefix_len = prefix.len();
59    s.len() >= prefix_len
60        && s[..prefix_len]
61            .iter()
62            .zip(prefix)
63            .all(|(&c, &p)| c.to_ascii_lowercase() == p)
64}
65
66// Reads an optional sign followed by decimal digits, saturating at the bounds of `i64`. Returns the
67// value and the number of bytes read, which is zero when there are no digits (in which case the
68// value is zero too).
69//
70// This is the `strtol` call in `parse_string` from `strtofr.c`, MPFR 4.3.0, together with the
71// clamping to `MPFR_EXP_MIN` and `MPFR_EXP_MAX` that follows it. Leading whitespace is not skipped:
72// the caller has already checked that the first character is not a space.
73fn read_exponent(s: &[u8]) -> (i64, usize) {
74    let mut i = 0;
75    let negative = s.first() == Some(&b'-');
76    if negative || s.first() == Some(&b'+') {
77        i = 1;
78    }
79    let start = i;
80    let mut exp = 0i64;
81    while let Some(&c) = s.get(i)
82        && c.is_ascii_digit()
83    {
84        exp = exp
85            .saturating_mul(10)
86            .saturating_add(i64::from(c - b'0') * if negative { -1 } else { 1 });
87        i += 1;
88    }
89    if i == start { (0, 0) } else { (exp, i) }
90}
91
92// The outcome of `parse_string`, corresponding to its return values: `Invalid` is -1, the special
93// values and `Zero` are 0, `Finite` is 1, and `Overflow` is 2. The `bool` fields are signs, `true`
94// meaning positive, the opposite of MPFR's `negative` field.
95#[derive(Clone, Debug, Eq, PartialEq)]
96enum ParsedString {
97    Invalid,
98    NaN,
99    Infinity(bool),
100    Zero(bool),
101    // The sign, the resolved base, the digit values (most significant first, with leading and
102    // trailing zeros stripped), the number of digits before the point plus any base exponent, and
103    // any binary exponent.
104    Finite(bool, u8, Vec<u8>, i64, i64),
105    Overflow(bool),
106}
107
108// Parses `s` in base `base`, which is 0 (detect the base from the prefix, defaulting to 10) or
109// between 2 and 62. Returns the parsed value and the number of bytes consumed, which is zero when
110// the input is invalid.
111//
112// This is `parse_string` from `strtofr.c`, MPFR 4.3.0.
113fn parse_string(s: &[u8], mut base: u8) -> (ParsedString, usize) {
114    let at = |i: usize| s.get(i).copied().unwrap_or(0);
115    let mut i = 0;
116    // optional leading whitespace
117    while at(i) != 0 && is_space(at(i)) {
118        i += 1;
119    }
120    // an optional sign
121    let sign = at(i) != b'-';
122    if at(i) == b'-' || at(i) == b'+' {
123        i += 1;
124    }
125    // a case-insensitive NaN
126    let nan = if starts_with_ignore_case(&s[i..], b"@nan@") {
127        i += 5;
128        true
129    } else if base <= 16 && starts_with_ignore_case(&s[i..], b"nan") {
130        i += 3;
131        true
132    } else {
133        false
134    };
135    if nan {
136        // an optional "(dummychars)"
137        if at(i) == b'(' {
138            let mut j = i + 1;
139            while at(j) != b')' {
140                if !at(j).is_ascii_alphanumeric() && at(j) != b'_' {
141                    break;
142                }
143                j += 1;
144            }
145            if at(j) == b')' {
146                i = j + 1;
147            }
148        }
149        return (ParsedString::NaN, i);
150    }
151    // a case-insensitive infinity
152    let s_tail = &s[i..];
153    if starts_with_ignore_case(s_tail, b"@inf@") {
154        return (ParsedString::Infinity(sign), i + 5);
155    } else if base <= 16 {
156        if starts_with_ignore_case(s_tail, b"infinity") {
157            return (ParsedString::Infinity(sign), i + 8);
158        } else if starts_with_ignore_case(s_tail, b"inf") {
159            return (ParsedString::Infinity(sign), i + 3);
160        }
161    }
162    // For a base of 0 or 16 the string may carry a "0x" prefix, and for 0 or 2 a "0b" one.
163    let mut prefix_index = None;
164    if (base == 0 || base == 16) && at(i) == b'0' && (at(i + 1) | 0x20) == b'x' {
165        prefix_index = Some(i);
166        base = 16;
167        i += 2;
168    }
169    if (base == 0 || base == 2) && at(i) == b'0' && (at(i + 1) | 0x20) == b'b' {
170        prefix_index = Some(i);
171        base = 2;
172        i += 2;
173    }
174    if base == 0 {
175        base = 10;
176    }
177    // Read the mantissa digits.
178    let mut digits;
179    let mut exp_base;
180    let mut start = i;
181    loop {
182        digits = Vec::new();
183        let mut point = false;
184        exp_base = 0i64;
185        i = start;
186        // loop until an invalid character is read
187        loop {
188            let c = at(i);
189            i += 1;
190            if c == b'.' {
191                if point {
192                    // a second point stops the parse
193                    break;
194                }
195                point = true;
196                continue;
197            }
198            let Some(d) = digit_value_in_base(c, base) else {
199                break;
200            };
201            digits.push(d);
202            if !point {
203                exp_base += 1;
204            }
205        }
206        // the last character read was invalid
207        i -= 1;
208        if !digits.is_empty() {
209            break;
210        }
211        // There are no digits. If a prefix was skipped, read the mantissa again without skipping
212        // it, so that "0x" alone parses as the digit 0.
213        let Some(p) = prefix_index else {
214            return (ParsedString::Invalid, 0);
215        };
216        start = p;
217        prefix_index = None;
218    }
219    // an optional exponent (e or E, p or P, @)
220    let mut exp_bin = 0i64;
221    let mut overflow = false;
222    let c = at(i);
223    if (c == b'@' || (base <= 10 && (c | 0x20) == b'e')) && !is_space(at(i + 1)) {
224        let (read_exp, len) = read_exponent(&s[i + 1..]);
225        if len != 0 {
226            i += 1 + len;
227        }
228        match read_exp.checked_add(exp_base) {
229            Some(sum) => exp_base = sum,
230            // Since `exp_base` is nonnegative, the sum cannot overflow downwards. The overflow is
231            // only recorded, not returned: a mantissa that turns out to be all zeros still parses
232            // as an exact zero, which takes precedence.
233            None => overflow = true,
234        }
235    } else if (base == 2 || base == 16) && (c | 0x20) == b'p' && !is_space(at(i + 1)) {
236        let (read_exp, len) = read_exponent(&s[i + 1..]);
237        if len != 0 {
238            i += 1 + len;
239        }
240        exp_bin = read_exp;
241    }
242    // Remove the zeros at the beginning and the end of the mantissa.
243    let mut leading = 0;
244    while leading < digits.len() && digits[leading] == 0 {
245        leading += 1;
246        exp_base = exp_base.saturating_sub(1);
247    }
248    digits.drain(..leading);
249    while digits.last() == Some(&0) {
250        digits.pop();
251    }
252    if digits.is_empty() {
253        return (ParsedString::Zero(sign), i);
254    }
255    if overflow {
256        return (ParsedString::Overflow(sign), i);
257    }
258    (
259        ParsedString::Finite(sign, base, digits, exp_base, exp_bin),
260        i,
261    )
262}
263
264/// Converts a string to a [`Float`], reading as much of it as forms a valid number.
265///
266/// The value is the exact value of the digits read, rounded once to `prec` bits with `rm`. Returns
267/// that value, the [`Ordering`] of it against the string's exact value, and the number of bytes
268/// consumed, which is zero if the string does not begin with a valid number (in which case the
269/// value is zero and the [`Ordering`] is `Equal`).
270///
271/// This is MPFR's grammar rather than Malachite's, so it differs from
272/// [`from_sci_string_prec_round`](Float::from_sci_string_prec_round) in what it accepts; see
273/// [`from_string`](mod@crate::float::conversion::string::from_string) for the Malachite side.
274/// Leading whitespace is skipped, then an optional sign, then:
275/// - `nan` or `inf` or `infinity`, case-insensitively, when `base` is 16 or less, or `@nan@` or
276///   `@inf@` in any base. A `nan` may be followed by a parenthesized run of alphanumerics and
277///   underscores, as in `nan(_char_sequence)`.
278/// - Otherwise digits, with an optional point among them. Digits above 9 are the letters, with the
279///   case ignored when `base` is 36 or less; above that, `a`–`z` continue the sequence after
280///   `A`–`Z`, giving values 36 to 61.
281///
282/// A `base` of 0 means the base is taken from a `0x` or `0b` prefix, defaulting to 10. Those
283/// prefixes are also accepted when `base` is 16 or 2 respectively.
284///
285/// An exponent may follow the digits: `e` or `E` when `base` is 10 or less, `p` or `P` when `base`
286/// is 2 or 16, and `@` in any base. An `e` or `@` exponent is a power of `base`, while a `p`
287/// exponent is a power of 2. The exponent itself is always read in base 10, and saturates rather
288/// than wrapping.
289///
290/// # Worst-case complexity
291/// $T(n) = O(n (\log n)^2 \log\log n)$
292///
293/// $M(n) = O(n \log n)$
294///
295/// where $T$ is time, $M$ is additional memory, and $n$ is `max(s.len(), prec)`.
296///
297/// # Panics
298/// Panics if `base` is 1 or greater than 62, if `prec` is zero, or if `rm` is `Exact` but the
299/// string's value is not exactly representable with `prec` bits.
300///
301/// # Examples
302/// ```
303/// use core::cmp::Ordering::*;
304/// use malachite_base::rounding_modes::RoundingMode::*;
305/// use malachite_float::float::conversion::string::strtofr::strtofr;
306///
307/// let s = |s, base, prec, rm| {
308///     let (x, o, len) = strtofr(s, base, prec, rm);
309///     (x.to_string(), o, len)
310/// };
311///
312/// assert_eq!(s("1.5", 10, 10, Nearest), ("1.5000".to_string(), Equal, 3));
313/// assert_eq!(
314///     s("ff", 16, 53, Nearest),
315///     ("255.00000000000000".to_string(), Equal, 2)
316/// );
317///
318/// // 0.1 is not representable in binary, so it is rounded and the `Ordering` gives the direction.
319/// assert_eq!(s("0.1", 10, 4, Floor), ("0.0938".to_string(), Less, 3));
320/// assert_eq!(s("0.1", 10, 4, Ceiling), ("0.102".to_string(), Greater, 3));
321///
322/// // A base of 0 takes the base from the prefix.
323/// assert_eq!(
324///     s("0b1.1", 0, 53, Nearest),
325///     ("1.5000000000000000".to_string(), Equal, 5)
326/// );
327///
328/// // `e` is a power of the base and `p` a power of two; `@` works in any base.
329/// assert_eq!(
330///     s("1e5", 10, 53, Nearest),
331///     ("100000.00000000000".to_string(), Equal, 3)
332/// );
333/// assert_eq!(
334///     s("1@5", 16, 53, Nearest),
335///     ("1048576.0000000000".to_string(), Equal, 3)
336/// );
337///
338/// // The special values, and a string that is not a number at all.
339/// assert_eq!(s("nan", 10, 53, Nearest), ("NaN".to_string(), Equal, 3));
340/// assert_eq!(
341///     s("-inf", 10, 53, Nearest),
342///     ("-Infinity".to_string(), Equal, 4)
343/// );
344/// assert_eq!(s("abc", 10, 53, Nearest), ("0.0".to_string(), Equal, 0));
345/// ```
346///
347/// This is `mpfr_strtofr` from `strtofr.c`, MPFR 4.3.0.
348pub fn strtofr(s: &str, base: u8, prec: u64, rm: RoundingMode) -> (Float, Ordering, usize) {
349    assert!(base == 0 || (2..=MAX_BASE).contains(&base));
350    assert_ne!(prec, 0);
351    match parse_string(s.as_bytes(), base) {
352        // An error occurred, so zero is returned; it is exact, so the ternary value is zero too.
353        (ParsedString::Invalid, _) => (Float::ZERO, Equal, 0),
354        (ParsedString::NaN, len) => (Float::NAN, Equal, len),
355        (ParsedString::Infinity(sign), len) => (
356            if sign {
357                Float::INFINITY
358            } else {
359                Float::NEGATIVE_INFINITY
360            },
361            Equal,
362            len,
363        ),
364        (ParsedString::Zero(sign), len) => (
365            if sign {
366                Float::ZERO
367            } else {
368                Float::NEGATIVE_ZERO
369            },
370            Equal,
371            len,
372        ),
373        (ParsedString::Overflow(sign), len) => {
374            let (x, o) = overflow(sign, prec, rm);
375            (x, o, len)
376        }
377        (ParsedString::Finite(sign, base, digits, exp_base, exp_bin), len) => {
378            let (x, o) = set_str_helper(sign, &digits, base, exp_base, exp_bin, prec, rm);
379            (x, o, len)
380        }
381    }
382}
383
384/// Converts a string to a [`Float`], requiring that the whole string be a valid number.
385///
386/// This is [`strtofr`] with the trailing text disallowed: it returns the value and the [`Ordering`]
387/// of that value against the string's exact value, or `None` if the string is empty or is not
388/// entirely consumed. See [`strtofr`] for the grammar, which is MPFR's rather than Malachite's.
389///
390/// Note that trailing whitespace is trailing text, and so is rejected, even though leading
391/// whitespace is skipped.
392///
393/// # Worst-case complexity
394/// $T(n) = O(n (\log n)^2 \log\log n)$
395///
396/// $M(n) = O(n \log n)$
397///
398/// where $T$ is time, $M$ is additional memory, and $n$ is `max(s.len(), prec)`.
399///
400/// # Panics
401/// Panics if `base` is 1 or greater than 62, if `prec` is zero, or if `rm` is `Exact` but the
402/// string's value is not exactly representable with `prec` bits.
403///
404/// # Examples
405/// ```
406/// use core::cmp::Ordering::*;
407/// use malachite_base::rounding_modes::RoundingMode::*;
408/// use malachite_float::float::conversion::string::strtofr::set_str;
409///
410/// let s = |s, base, prec, rm| set_str(s, base, prec, rm).map(|(x, o)| (x.to_string(), o));
411///
412/// assert_eq!(
413///     s("1.5", 10, 10, Nearest),
414///     Some(("1.5000".to_string(), Equal))
415/// );
416/// assert_eq!(
417///     s("0.1", 10, 4, Nearest),
418///     Some(("0.102".to_string(), Greater))
419/// );
420///
421/// // Trailing text that `strtofr` would simply stop at is rejected here.
422/// assert_eq!(s("1.5abc", 10, 10, Nearest), None);
423/// assert_eq!(s("1.5 ", 10, 10, Nearest), None);
424/// assert_eq!(s("", 10, 10, Nearest), None);
425/// ```
426///
427/// This is `mpfr_set_str` from `set_str.c`, MPFR 4.3.0. MPFR's version reports only success or
428/// failure, discarding the ternary value; this one returns it.
429pub fn set_str(s: &str, base: u8, prec: u64, rm: RoundingMode) -> Option<(Float, Ordering)> {
430    if s.is_empty() {
431        return None;
432    }
433    let (x, o, len) = strtofr(s, base, prec, rm);
434    if len == s.len() { Some((x, o)) } else { None }
435}