1#[derive(Clone, Copy, PartialEq, Debug)]
10pub enum Num {
11 Int(i64),
13 Float(f64),
15}
16
17impl Num {
18 pub fn as_f64(self) -> f64 {
21 match self {
22 Num::Int(i) => i as f64,
23 Num::Float(f) => f,
24 }
25 }
26
27 fn negate(self) -> Num {
28 match self {
29 Num::Int(i) => Num::Int(i.wrapping_neg()),
30 Num::Float(f) => Num::Float(-f),
31 }
32 }
33}
34
35pub fn hex_digit(c: u8) -> Option<u32> {
38 match c {
39 b'0'..=b'9' => Some((c - b'0') as u32),
40 b'a'..=b'f' => Some((c - b'a' + 10) as u32),
41 b'A'..=b'F' => Some((c - b'A' + 10) as u32),
42 _ => None,
43 }
44}
45
46pub fn dec_literal(text: &[u8], int_ok: bool, neg: bool) -> Option<Num> {
53 let mut i = 0;
54 let mut int_digits = 0;
55 while i < text.len() && text[i].is_ascii_digit() {
56 i += 1;
57 int_digits += 1;
58 }
59 let mut frac_digits = 0;
60 let mut has_dot = false;
61 if i < text.len() && text[i] == b'.' {
62 has_dot = true;
63 i += 1;
64 while i < text.len() && text[i].is_ascii_digit() {
65 i += 1;
66 frac_digits += 1;
67 }
68 }
69 if int_digits + frac_digits == 0 {
70 return None;
71 }
72 let mut has_exp = false;
73 if i < text.len() && matches!(text[i], b'e' | b'E') {
74 has_exp = true;
75 i += 1;
76 if i < text.len() && matches!(text[i], b'+' | b'-') {
77 i += 1;
78 }
79 let mut digits = 0;
80 while i < text.len() && text[i].is_ascii_digit() {
81 i += 1;
82 digits += 1;
83 }
84 if digits == 0 {
85 return None;
86 }
87 }
88 if i != text.len() {
89 return None;
90 }
91 let s = str::from_utf8(text).expect("ascii numeral");
92 if !has_dot && !has_exp && int_ok {
93 const MAXBY10: u64 = i64::MAX as u64 / 10;
99 const MAXLAST: u64 = i64::MAX as u64 % 10;
100 let mut a: u64 = 0;
101 let mut overflow = false;
102 for &c in s.as_bytes() {
103 let d = (c - b'0') as u64;
104 if a >= MAXBY10 && (a > MAXBY10 || d > MAXLAST + neg as u64) {
105 overflow = true;
106 break;
107 }
108 a = a * 10 + d;
109 }
110 if !overflow {
111 return Some(Num::Int(a as i64));
112 }
113 }
114 s.parse::<f64>().ok().map(Num::Float)
115}
116
117pub fn hex_literal(text: &[u8], int_ok: bool, float_ok: bool) -> Option<Num> {
119 let mut i = 0;
120 while i < text.len() && hex_digit(text[i]).is_some() {
121 i += 1;
122 }
123 let int_end = i;
124 let mut has_dot = false;
125 let mut frac = 0..0;
126 if i < text.len() && text[i] == b'.' {
127 has_dot = true;
128 i += 1;
129 let fs = i;
130 while i < text.len() && hex_digit(text[i]).is_some() {
131 i += 1;
132 }
133 frac = fs..i;
134 }
135 if int_end + frac.len() == 0 {
136 return None;
137 }
138 let has_exp = i < text.len() && matches!(text[i], b'p' | b'P');
139 let mut pexp: i64 = 0;
140 if has_exp {
141 i += 1;
142 let mut sign = 1i64;
143 if i < text.len() && matches!(text[i], b'+' | b'-') {
144 sign = if text[i] == b'-' { -1 } else { 1 };
145 i += 1;
146 }
147 let mut digits = 0;
148 let mut e: i64 = 0;
149 while i < text.len() && text[i].is_ascii_digit() {
150 e = (e * 10 + (text[i] - b'0') as i64).min(1 << 40);
151 i += 1;
152 digits += 1;
153 }
154 if digits == 0 {
155 return None;
156 }
157 pexp = sign * e;
158 }
159 if i != text.len() {
160 return None;
161 }
162 if !has_exp && !has_dot {
163 if int_ok {
164 let mut v: u64 = 0;
166 for &c in &text[..int_end] {
167 v = v
168 .wrapping_mul(16)
169 .wrapping_add(hex_digit(c).unwrap() as u64);
170 }
171 return Some(Num::Int(v as i64));
172 }
173 let mut v: f64 = 0.0;
178 for &c in &text[..int_end] {
179 v = v * 16.0 + hex_digit(c).unwrap() as f64;
180 }
181 return Some(Num::Float(v));
182 }
183 if !float_ok {
184 return None;
185 }
186 let mut mant: u64 = 0;
189 let mut sticky = false;
190 let mut exp4: i64 = 0;
191 for &c in &text[..int_end] {
192 let d = hex_digit(c).unwrap() as u64;
193 if mant >> 60 == 0 {
194 mant = mant * 16 + d;
195 } else {
196 sticky |= d != 0;
197 exp4 += 1;
198 }
199 }
200 for &c in &text[frac] {
201 let d = hex_digit(c).unwrap() as u64;
202 if mant >> 60 == 0 {
203 mant = mant * 16 + d;
204 exp4 -= 1;
205 } else {
206 sticky |= d != 0;
207 }
208 }
209 Some(Num::Float(compose_f64(mant, sticky, exp4 * 4 + pexp)))
210}
211
212pub fn str2num(s: &[u8], int_ok: bool, hex_float_ok: bool) -> Option<Num> {
215 let is_space = |c: &&u8| matches!(**c, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
216 let mut s = s;
217 while s.first().filter(is_space).is_some() {
218 s = &s[1..];
219 }
220 while s.last().filter(is_space).is_some() {
221 s = &s[..s.len() - 1];
222 }
223 let neg = match s.first() {
224 Some(b'-') => {
225 s = &s[1..];
226 true
227 }
228 Some(b'+') => {
229 s = &s[1..];
230 false
231 }
232 _ => false,
233 };
234 let n = if s.len() > 2 && s[0] == b'0' && matches!(s[1], b'x' | b'X') {
235 hex_literal(&s[2..], int_ok, hex_float_ok)?
236 } else {
237 dec_literal(s, int_ok, neg)?
238 };
239 Some(if neg { n.negate() } else { n })
240}
241
242pub fn strtod_str(s: &[u8]) -> Option<f64> {
249 let is_space = |c: u8| matches!(c, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
250 let s = &s[..s.iter().position(|&c| c == 0).unwrap_or(s.len())];
251 let mut i = 0;
252 while i < s.len() && is_space(s[i]) {
253 i += 1;
254 }
255 let neg = match s.get(i) {
256 Some(b'-') => {
257 i += 1;
258 true
259 }
260 Some(b'+') => {
261 i += 1;
262 false
263 }
264 _ => false,
265 };
266 let rest = &s[i..];
267 let starts = |w: &[u8]| rest.len() >= w.len() && rest[..w.len()].eq_ignore_ascii_case(w);
268 let (v, used) = if starts(b"infinity") {
269 (f64::INFINITY, 8)
270 } else if starts(b"inf") {
271 (f64::INFINITY, 3)
272 } else if starts(b"nan") {
273 let mut n = 3;
274 if rest.get(3) == Some(&b'(') {
276 let close = rest[4..]
277 .iter()
278 .position(|&c| !(c.is_ascii_alphanumeric() || c == b'_'));
279 if let Some(k) = close
280 && rest[4 + k] == b')'
281 {
282 n = 5 + k;
283 }
284 }
285 (f64::NAN, n)
286 } else if rest.len() > 2
287 && rest[0] == b'0'
288 && matches!(rest[1], b'x' | b'X')
289 && let Some((v, n)) = hex_prefix(&rest[2..])
290 {
291 (v, 2 + n)
292 } else {
293 dec_prefix(rest)?
294 };
295 if !rest[used..].iter().all(|&c| is_space(c)) {
296 return None;
297 }
298 Some(if neg { -v } else { v })
299}
300
301fn dec_prefix(s: &[u8]) -> Option<(f64, usize)> {
304 let digits = |s: &[u8], mut i: usize| {
305 while i < s.len() && s[i].is_ascii_digit() {
306 i += 1;
307 }
308 i
309 };
310 let int_end = digits(s, 0);
311 let mut i = int_end;
312 let mut frac = 0;
313 if i < s.len() && s[i] == b'.' {
314 let e = digits(s, i + 1);
315 frac = e - (i + 1);
316 i = e;
317 }
318 if int_end + frac == 0 {
319 return None;
320 }
321 if i < s.len() && matches!(s[i], b'e' | b'E') {
322 let mut j = i + 1;
323 if j < s.len() && matches!(s[j], b'+' | b'-') {
324 j += 1;
325 }
326 let e = digits(s, j);
327 if e > j {
328 i = e;
329 }
330 }
331 let text = str::from_utf8(&s[..i]).expect("ascii numeral");
332 Some((text.parse::<f64>().ok()?, i))
333}
334
335fn hex_prefix(s: &[u8]) -> Option<(f64, usize)> {
338 let hexes = |mut i: usize| {
339 while i < s.len() && hex_digit(s[i]).is_some() {
340 i += 1;
341 }
342 i
343 };
344 let int_end = hexes(0);
345 let mut i = int_end;
346 let mut frac = 0;
347 if i < s.len() && s[i] == b'.' {
348 let e = hexes(i + 1);
349 frac = e - (i + 1);
350 i = e;
351 }
352 if int_end + frac == 0 {
353 return None;
354 }
355 if i < s.len() && matches!(s[i], b'p' | b'P') {
356 let mut j = i + 1;
357 if j < s.len() && matches!(s[j], b'+' | b'-') {
358 j += 1;
359 }
360 let mut e = j;
361 while e < s.len() && s[e].is_ascii_digit() {
362 e += 1;
363 }
364 if e > j {
365 i = e;
366 }
367 }
368 hex_literal(&s[..i], false, true).map(|n| (n.as_f64(), i))
369}
370
371fn compose_f64(mant: u64, sticky: bool, exp: i64) -> f64 {
373 if mant == 0 {
374 return 0.0;
375 }
376 let bits = 64 - mant.leading_zeros() as i64;
377 let (m, extra) = if bits <= 53 {
378 (mant, 0i64)
379 } else {
380 let excess = (bits - 53) as u32;
381 let kept = mant >> excess;
382 let rem = mant & ((1u64 << excess) - 1);
383 let half = 1u64 << (excess - 1);
384 let round_up = rem > half || (rem == half && (sticky || kept & 1 == 1));
385 (kept + round_up as u64, excess as i64)
386 };
387 scale_f64(m as f64, exp + extra)
388}
389
390fn exp2(e: i64) -> f64 {
391 debug_assert!((-1022..=1023).contains(&e));
392 f64::from_bits(((e + 1023) as u64) << 52)
393}
394
395fn scale_f64(mut f: f64, mut e: i64) -> f64 {
396 while e > 1023 {
397 f *= exp2(1023);
398 e -= 1023;
399 if f.is_infinite() {
400 return f;
401 }
402 }
403 while e < -1022 {
404 f *= exp2(-1022);
405 e += 1022;
406 if f == 0.0 {
407 return f;
408 }
409 }
410 f * exp2(e)
411}
412
413pub fn num_to_string(n: Num) -> String {
420 num_to_string_for(n, FloatFmt::TwoStage55)
421}
422
423#[inline]
429pub fn write_i64_dec(i: i64, buf: &mut [u8; 20]) -> &[u8] {
430 if i == 0 {
431 buf[0] = b'0';
432 return &buf[..1];
433 }
434 let neg = i < 0;
435 let mut n = i.unsigned_abs();
437 let mut pos = 20;
438 while n > 0 {
439 pos -= 1;
440 buf[pos] = b'0' + (n % 10) as u8;
441 n /= 10;
442 }
443 if neg {
444 pos -= 1;
445 buf[pos] = b'-';
446 }
447 &buf[pos..]
448}
449
450#[derive(Clone, Copy, PartialEq, Eq, Debug)]
454pub enum FloatFmt {
455 Legacy14,
458 G14,
460 TwoStage55,
463}
464
465pub fn num_to_string_for(n: Num, fmt: FloatFmt) -> String {
468 match n {
469 Num::Int(i) => i.to_string(),
470 Num::Float(f) => float_to_string(f, fmt),
471 }
472}
473
474fn format_g(f: f64, prec: usize) -> String {
479 debug_assert!(prec >= 1);
480 let sci = format!("{f:.*e}", prec - 1);
483 let epos = sci.rfind('e').expect("scientific form has exponent");
484 let exp: i32 = sci[epos + 1..].parse().expect("valid exponent");
485 if exp < -4 || exp >= prec as i32 {
486 let mut mant = sci[..epos].to_string();
487 if mant.contains('.') {
488 while mant.ends_with('0') {
489 mant.pop();
490 }
491 if mant.ends_with('.') {
492 mant.pop();
493 }
494 }
495 let (esign, eabs) = if exp < 0 { ('-', -exp) } else { ('+', exp) };
496 format!("{mant}e{esign}{eabs:02}")
497 } else {
498 let decimals = (prec as i32 - 1 - exp).max(0) as usize;
499 let mut s = format!("{f:.decimals$}");
500 if s.contains('.') {
501 while s.ends_with('0') {
502 s.pop();
503 }
504 if s.ends_with('.') {
505 s.pop();
506 }
507 }
508 s
509 }
510}
511
512pub(crate) fn nan_spelling(f: f64) -> (bool, &'static str) {
521 let negative = f.is_sign_negative();
522 if cfg!(target_vendor = "apple") {
523 (false, "nan")
524 } else if cfg!(target_os = "windows") {
525 let bits = f.to_bits();
526 let quiet = bits & (1 << 51) != 0;
527 let body = if !quiet {
528 "nan(snan)"
529 } else if bits == 0xFFF8_0000_0000_0000 {
530 "nan(ind)"
531 } else {
532 "nan"
533 };
534 (negative, body)
535 } else {
536 (negative, "nan")
537 }
538}
539
540fn float_to_string(f: f64, fmt: FloatFmt) -> String {
541 if f.is_nan() {
542 let (negative, body) = nan_spelling(f);
543 return if negative {
544 format!("-{body}")
545 } else {
546 body.to_string()
547 };
548 }
549 if f.is_infinite() {
550 return if f < 0.0 { "-inf" } else { "inf" }.to_string();
551 }
552 let mut s = match fmt {
553 FloatFmt::Legacy14 | FloatFmt::G14 => format_g(f, 14),
555 FloatFmt::TwoStage55 => {
558 let first = format_g(f, 15);
559 if first.parse::<f64>() == Ok(f) {
560 first
561 } else {
562 format_g(f, 17)
563 }
564 }
565 };
566 if s.bytes().all(|c| c.is_ascii_digit() || c == b'-') && fmt != FloatFmt::Legacy14 {
567 s.push_str(".0");
568 }
569 s
570}
571
572#[cfg(test)]
573mod tests {
574 #[test]
575 fn strtod_str_reads_what_c_strtod_reads() {
576 assert_eq!(strtod_str(b" 10 "), Some(10.0));
577 assert_eq!(strtod_str(b"10\0zz"), Some(10.0));
578 assert_eq!(strtod_str(b"-inf"), Some(f64::NEG_INFINITY));
579 assert_eq!(strtod_str(b"Infinity"), Some(f64::INFINITY));
580 assert!(strtod_str(b"nan").is_some_and(f64::is_nan));
581 assert!(strtod_str(b"nan(123)").is_some_and(f64::is_nan));
582 assert_eq!(strtod_str(b"0x1p4"), Some(16.0));
583 assert_eq!(strtod_str(b"0x.8"), Some(0.5));
584 assert_eq!(strtod_str(b".5e1"), Some(5.0));
585 assert_eq!(strtod_str(b"0x"), None);
586 assert_eq!(strtod_str(b"1e"), None);
587 assert_eq!(strtod_str(b"infx"), None);
588 assert_eq!(strtod_str(b""), None);
589 }
590
591 use super::*;
592
593 #[test]
594 fn str2num_semantics() {
595 assert_eq!(str2num(b" 42 ", true, true), Some(Num::Int(42)));
596 assert_eq!(str2num(b"-10", true, true), Some(Num::Int(-10)));
597 assert_eq!(str2num(b"+0x10", true, true), Some(Num::Int(16)));
598 assert_eq!(str2num(b"-0x10", true, true), Some(Num::Int(-16)));
599 assert_eq!(str2num(b" 0x1p4 ", true, true), Some(Num::Float(16.0)));
600 assert_eq!(str2num(b"3.5", true, true), Some(Num::Float(3.5)));
601 assert_eq!(str2num(b"1e3", true, true), Some(Num::Float(1000.0)));
602 assert_eq!(str2num(b"", true, true), None);
603 assert_eq!(str2num(b" - 1", true, true), None);
604 assert_eq!(str2num(b"10a", true, true), None);
605 assert_eq!(str2num(b"0x", true, true), None);
606 assert_eq!(str2num(b"42", false, false), Some(Num::Float(42.0)));
608 assert_eq!(str2num(b"0x1p4", false, false), None);
609 assert_eq!(
613 str2num(b"-9223372036854775808", true, true),
614 Some(Num::Int(i64::MIN))
615 );
616 assert_eq!(
617 str2num(b"9223372036854775807", true, true),
618 Some(Num::Int(i64::MAX))
619 );
620 assert_eq!(
621 str2num(b"9223372036854775808", true, true),
622 Some(Num::Float(9223372036854775808.0))
623 );
624 }
625
626 #[test]
627 fn number_printing() {
628 assert_eq!(num_to_string(Num::Int(42)), "42");
629 assert_eq!(num_to_string(Num::Int(-1)), "-1");
630 assert_eq!(num_to_string(Num::Float(2.0)), "2.0");
631 assert_eq!(num_to_string(Num::Float(-2.0)), "-2.0");
632 assert_eq!(num_to_string(Num::Float(0.5)), "0.5");
633 assert_eq!(num_to_string(Num::Float(1e300)), "1e+300");
634 assert_eq!(num_to_string(Num::Float(1e-7)), "1e-07");
635 assert_eq!(num_to_string(Num::Float(1e15)), "1e+15");
636 assert_eq!(num_to_string(Num::Float(100.0)), "100.0");
637 assert_eq!(num_to_string(Num::Float(f64::INFINITY)), "inf");
638 assert_eq!(num_to_string(Num::Float(f64::NAN)), "nan");
639 assert_eq!(num_to_string(Num::Float(0.1)), "0.1");
643 assert_eq!(num_to_string(Num::Float(1.0 / 3.0)), "0.33333333333333331");
644 assert_eq!(
645 num_to_string(Num::Float(std::f64::consts::PI)),
646 "3.1415926535897931"
647 );
648 assert_eq!(num_to_string(Num::Float(1e14)), "100000000000000.0");
649 assert_eq!(
650 num_to_string(Num::Float(9007199254740992.0)),
651 "9007199254740992.0"
652 );
653 assert_eq!(num_to_string(Num::Float(5e-324)), "4.94065645841247e-324");
654 }
655
656 #[test]
657 fn hex_float_rounding() {
658 let Some(Num::Float(f)) = hex_literal(b"1FFFFFFFFFFFFF8.0p0", true, true) else {
661 panic!()
662 };
663 assert_eq!(f, 0x1FFFFFFFFFFFFF8u64 as f64);
664 let Some(Num::Float(g)) = hex_literal(b"1.8p1", true, true) else {
665 panic!()
666 };
667 assert_eq!(g, 3.0);
668 let Some(Num::Float(h)) = hex_literal(b"0.8", true, true) else {
669 panic!()
670 };
671 assert_eq!(h, 0.5);
672 }
673}