Skip to main content

base/
num.rs

1// num -- numbers <-> text                                                  [src/core/ini.s]
2//
3//   f64_parse(s) -> (value, used)   leading blanks, sign, digits, '.', exponent, inf /
4//                                   infinity / nan; used = 0 when there is no number
5//   f64_format(v) -> String         the shortest text that reads back to the same double,
6//                                   '.' separator, "1e-07" style below 1e-5 and from 1e17,
7//                                   "inf" / "-inf" / "nan"
8//   parse_i64(s) -> Option<i64>     decimal (+/-), 0x hex, true/false/yes/no/on/off (the whole
9//                                   string; overflow -> None)
10//
11// The scaling steps run on the x87 unit like in ASMLIB (rt::x87), so both directions are
12// bit-identical to the assembly -- the text of a saved setting included. Where the unit holds
13// only doubles (x64 under the emulation of Windows on ARM, docs/ARM64.md), rt::portable::math
14// does the same steps without it.
15
16use alloc::string::String;
17use alloc::vec::Vec;
18
19const EXP_MAX: i32 = 5000; // decimal exponents are clamped to +-EXP_MAX
20
21const POW10_INT: [u64; 19] = [
22    1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000,
23    1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000, 100000000000000000,
24    1000000000000000000,
25];
26
27#[inline]
28fn at(s: &[u8], i: usize) -> u8 {
29    if i < s.len() { s[i] } else { 0 }
30}
31
32/// s starts with the lower-case word, ASCII case-insensitive (stops at the end of s)  [ieq_z]
33fn ieq_z(s: &[u8], i: usize, word: &[u8]) -> bool {
34    word.iter().enumerate().all(|(k, &w)| {
35        let c = at(s, i + k);
36        c != 0 && c | 32 == w
37    })
38}
39
40/// whether the x87 unit really has its 64-bit significands and wide exponents: under the emulation
41/// of Windows on ARM its registers hold only doubles, whatever the control word asks for, so
42/// 10^324 overflows and the rounding differs
43#[cfg(not(target_arch = "wasm32"))]
44fn x87_is_real() -> bool {
45    use core::sync::atomic::{AtomicU8, Ordering};
46    static REAL: AtomicU8 = AtomicU8::new(0); // 0: not known yet, 1: yes, 2: no
47    match REAL.load(Ordering::Relaxed) {
48        0 => {
49            let real = rt::x87::scale(5, -324).to_bits() == 1
50                && rt::x87::scale(3, 100).to_bits() == 0x54CB_6E83_B85F_253B;
51            REAL.store(if real { 1 } else { 2 }, Ordering::Relaxed);
52            real
53        }
54        r => r == 1,
55    }
56}
57
58/// rt::x87::scale, or the same steps without the unit
59fn scale(m: u64, e: i32) -> f64 {
60    #[cfg(not(target_arch = "wasm32"))]
61    if !x87_is_real() {
62        return rt::portable::math::scale(m, e);
63    }
64    rt::x87::scale(m, e)
65}
66
67/// rt::x87::scale_round, or the same steps without the unit
68fn scale_round(v: f64, k: i32) -> i64 {
69    #[cfg(not(target_arch = "wasm32"))]
70    if !x87_is_real() {
71        return rt::portable::math::scale_round(v, k);
72    }
73    rt::x87::scale_round(v, k)
74}
75
76/// the number at the start of s: (value, bytes used; 0 = none)  [f64_parse]
77pub fn f64_parse(s: &[u8]) -> (f64, usize) {
78    let mut i = 0;
79    while matches!(at(s, i), b' ' | b'\t') {
80        i += 1;
81    }
82    let mut neg = false;
83    match at(s, i) {
84        b'-' => {
85            neg = true;
86            i += 1;
87        }
88        b'+' => i += 1,
89        _ => {}
90    }
91    let signed = |v: f64| if neg { f64::from_bits(v.to_bits() ^ 1 << 63) } else { v };
92    match at(s, i) | 32 {
93        b'i' => {
94            if at(s, i + 1) | 32 != b'n' || at(s, i + 2) | 32 != b'f' {
95                return (0.0, 0);
96            }
97            i += 3;
98            if ieq_z(s, i, b"inity") {
99                i += 5;
100            }
101            return (signed(f64::INFINITY), i);
102        }
103        b'n' => {
104            if at(s, i + 1) | 32 != b'a' || at(s, i + 2) | 32 != b'n' {
105                return (0.0, 0);
106            }
107            return (signed(f64::from_bits(0x7FF8_0000_0000_0000)), i + 3);
108        }
109        _ => {}
110    }
111    let mut m: u64 = 0; // at most 18 digits
112    let mut kept = 0;
113    let mut e10: i32 = 0;
114    let mut seen = 0;
115    loop {
116        let d = at(s, i).wrapping_sub(b'0');
117        if d > 9 {
118            break;
119        }
120        seen += 1;
121        i += 1;
122        if m == 0 && d == 0 {
123            continue; // leading zero
124        }
125        if kept < 18 {
126            m = m * 10 + d as u64;
127            kept += 1;
128        } else if e10 < 100000 {
129            e10 += 1;
130        }
131    }
132    if at(s, i) == b'.' {
133        i += 1;
134        loop {
135            let d = at(s, i).wrapping_sub(b'0');
136            if d > 9 {
137                break;
138            }
139            seen += 1;
140            i += 1;
141            if m == 0 && d == 0 {
142                if e10 > -100000 {
143                    e10 -= 1; // 0.000...: only the exponent moves
144                }
145                continue;
146            }
147            if kept < 18 {
148                m = m * 10 + d as u64;
149                kept += 1;
150                e10 -= 1;
151            }
152        }
153    }
154    if seen == 0 {
155        return (0.0, 0);
156    }
157    if at(s, i) | 32 == b'e' {
158        let mut j = i + 1;
159        let mut eneg = false;
160        match at(s, j) {
161            b'-' => {
162                eneg = true;
163                j += 1;
164            }
165            b'+' => j += 1,
166            _ => {}
167        }
168        if at(s, j).wrapping_sub(b'0') <= 9 {
169            let mut ed: i32 = 0;
170            loop {
171                let d = at(s, j).wrapping_sub(b'0');
172                if d > 9 {
173                    break;
174                }
175                if ed < 100000 {
176                    ed = ed * 10 + d as i32;
177                }
178                j += 1;
179            }
180            i = j;
181            e10 += if eneg { -ed } else { ed };
182        } // "e" without digits is not ours
183    }
184    let v = if m == 0 { 0.0 } else { scale(m, e10.clamp(-EXP_MAX, EXP_MAX)) };
185    (signed(v), i)
186}
187
188/// the p-digit integer n (10^(p-1) <= n < 10^p) as n * 10^(e10-p+1), no trailing zeros  [f64_render]
189fn render(mut n: u64, p: usize, e10: i32) -> Vec<u8> {
190    let mut dig = [0u8; 17];
191    for c in (0..p).rev() {
192        dig[c] = b'0' + (n % 10) as u8;
193        n /= 10;
194    }
195    let mut sig = p;
196    while sig > 1 && dig[sig - 1] == b'0' {
197        sig -= 1;
198    }
199    let dig = &dig[..sig];
200    let mut out = Vec::with_capacity(32);
201    if (-5..17).contains(&e10) {
202        if e10 >= 0 {
203            let int = e10 as usize + 1;
204            for c in 0..int {
205                out.push(if c < sig { dig[c] } else { b'0' });
206            }
207            if sig > int {
208                out.push(b'.');
209                out.extend_from_slice(&dig[int..]);
210            }
211        } else {
212            out.extend_from_slice(b"0.");
213            for _ in 0..(-e10 - 1) {
214                out.push(b'0');
215            }
216            out.extend_from_slice(dig);
217        }
218    } else {
219        out.push(dig[0]);
220        if sig > 1 {
221            out.push(b'.');
222            out.extend_from_slice(&dig[1..]);
223        }
224        out.push(b'e');
225        if e10 < 0 {
226            out.push(b'-');
227        }
228        let a = e10.unsigned_abs();
229        if a < 10 {
230            out.push(b'0'); // at least two exponent digits: 1e-07
231        }
232        if a >= 100 {
233            out.push(b'0' + (a / 100) as u8);
234            out.push(b'0' + (a % 100 / 10) as u8);
235        } else if a >= 10 {
236            out.push(b'0' + (a / 10) as u8);
237        }
238        out.push(b'0' + (a % 10) as u8);
239    }
240    out
241}
242
243/// the shortest decimal text that reads back to v  [f64_format]
244pub fn f64_format(v: f64) -> String {
245    let bits = v.to_bits();
246    let mut neg = bits >> 63 != 0;
247    let abs = bits & !(1 << 63);
248    let text: Vec<u8> = if abs >> 52 == 0x7FF {
249        if abs << 12 != 0 {
250            neg = false;
251            b"nan".to_vec()
252        } else {
253            b"inf".to_vec()
254        }
255    } else if abs == 0 {
256        b"0".to_vec()
257    } else {
258        let av = f64::from_bits(abs);
259        let mut e10 = rt::x87::log10_round(av); // decimal exponent, +-1 (fixed below)
260        let mut p = 1usize; // significant digits
261        loop {
262            let mut budget = 4;
263            let n = loop {
264                let k = p as i32 - 1 - e10;
265                let n = scale_round(av, k) as u64;
266                if n >= POW10_INT[p] {
267                    e10 += 1; // the exponent was too small
268                } else if n < POW10_INT[p - 1] {
269                    e10 -= 1; // too large
270                } else {
271                    break n;
272                }
273                budget -= 1;
274                if budget == 0 {
275                    break n;
276                }
277            };
278            let t = render(n, p, e10);
279            if f64_parse(&t).0.to_bits() == abs || p >= 17 {
280                break t;
281            }
282            p += 1;
283        }
284    };
285    let mut s = String::with_capacity(text.len() + 1);
286    if neg {
287        s.push('-');
288    }
289    s.push_str(core::str::from_utf8(&text).unwrap_or("0"));
290    s
291}
292
293/// an integer setting: the whole string must match  [parse_i64]
294pub fn parse_i64(s: &[u8]) -> Option<i64> {
295    const WORDS: [(&[u8], i64); 6] =
296        [(b"true", 1), (b"false", 0), (b"yes", 1), (b"no", 0), (b"on", 1), (b"off", 0)];
297    for (w, v) in WORDS {
298        if ieq_z(s, 0, w) && at(s, w.len()) == 0 {
299            return Some(v);
300        }
301    }
302    let mut i = 0;
303    let mut neg = false;
304    match at(s, 0) {
305        b'-' => {
306            neg = true;
307            i = 1;
308        }
309        b'+' => i = 1,
310        _ => {}
311    }
312    let mut v: u64 = 0;
313    let mut digits = 0;
314    if at(s, i) == b'0' && at(s, i + 1) | 32 == b'x' {
315        i += 2;
316        loop {
317            let c = at(s, i);
318            let d = match c {
319                b'0'..=b'9' => c - b'0',
320                _ if (b'a'..=b'f').contains(&(c | 32)) => (c | 32) - 87,
321                _ => break,
322            };
323            if digits >= 16 {
324                return None; // more than 64 bits
325            }
326            v = v << 4 | d as u64;
327            digits += 1;
328            i += 1;
329        }
330        if v > i64::MAX as u64 + neg as u64 {
331            return None; // not a bit pattern: the value must fit
332        }
333    } else {
334        loop {
335            let d = at(s, i).wrapping_sub(b'0');
336            if d > 9 {
337                break;
338            }
339            if v > 922337203685477580 {
340                return None;
341            }
342            v = v * 10 + d as u64;
343            if v > i64::MAX as u64 + neg as u64 {
344                return None; // |min| is one more than max
345            }
346            digits += 1;
347            i += 1;
348        }
349    }
350    if digits == 0 || at(s, i) != 0 {
351        return None;
352    }
353    let v = v as i64;
354    Some(if neg { v.wrapping_neg() } else { v })
355}