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