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}