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}