#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Num {
Int(i64),
Float(f64),
}
impl Num {
pub fn as_f64(self) -> f64 {
match self {
Num::Int(i) => i as f64,
Num::Float(f) => f,
}
}
fn negate(self) -> Num {
match self {
Num::Int(i) => Num::Int(i.wrapping_neg()),
Num::Float(f) => Num::Float(-f),
}
}
}
pub fn hex_digit(c: u8) -> Option<u32> {
match c {
b'0'..=b'9' => Some((c - b'0') as u32),
b'a'..=b'f' => Some((c - b'a' + 10) as u32),
b'A'..=b'F' => Some((c - b'A' + 10) as u32),
_ => None,
}
}
pub fn dec_literal(text: &[u8], int_ok: bool, neg: bool) -> Option<Num> {
let mut i = 0;
let mut int_digits = 0;
while i < text.len() && text[i].is_ascii_digit() {
i += 1;
int_digits += 1;
}
let mut frac_digits = 0;
let mut has_dot = false;
if i < text.len() && text[i] == b'.' {
has_dot = true;
i += 1;
while i < text.len() && text[i].is_ascii_digit() {
i += 1;
frac_digits += 1;
}
}
if int_digits + frac_digits == 0 {
return None;
}
let mut has_exp = false;
if i < text.len() && matches!(text[i], b'e' | b'E') {
has_exp = true;
i += 1;
if i < text.len() && matches!(text[i], b'+' | b'-') {
i += 1;
}
let mut digits = 0;
while i < text.len() && text[i].is_ascii_digit() {
i += 1;
digits += 1;
}
if digits == 0 {
return None;
}
}
if i != text.len() {
return None;
}
let s = str::from_utf8(text).expect("ascii numeral");
if !has_dot && !has_exp && int_ok {
const MAXBY10: u64 = i64::MAX as u64 / 10;
const MAXLAST: u64 = i64::MAX as u64 % 10;
let mut a: u64 = 0;
let mut overflow = false;
for &c in s.as_bytes() {
let d = (c - b'0') as u64;
if a >= MAXBY10 && (a > MAXBY10 || d > MAXLAST + neg as u64) {
overflow = true;
break;
}
a = a * 10 + d;
}
if !overflow {
return Some(Num::Int(a as i64));
}
}
s.parse::<f64>().ok().map(Num::Float)
}
pub fn hex_literal(text: &[u8], int_ok: bool, float_ok: bool) -> Option<Num> {
let mut i = 0;
while i < text.len() && hex_digit(text[i]).is_some() {
i += 1;
}
let int_end = i;
let mut has_dot = false;
let mut frac = 0..0;
if i < text.len() && text[i] == b'.' {
has_dot = true;
i += 1;
let fs = i;
while i < text.len() && hex_digit(text[i]).is_some() {
i += 1;
}
frac = fs..i;
}
if int_end + frac.len() == 0 {
return None;
}
let has_exp = i < text.len() && matches!(text[i], b'p' | b'P');
let mut pexp: i64 = 0;
if has_exp {
i += 1;
let mut sign = 1i64;
if i < text.len() && matches!(text[i], b'+' | b'-') {
sign = if text[i] == b'-' { -1 } else { 1 };
i += 1;
}
let mut digits = 0;
let mut e: i64 = 0;
while i < text.len() && text[i].is_ascii_digit() {
e = (e * 10 + (text[i] - b'0') as i64).min(1 << 40);
i += 1;
digits += 1;
}
if digits == 0 {
return None;
}
pexp = sign * e;
}
if i != text.len() {
return None;
}
if !has_exp && !has_dot {
if int_ok {
let mut v: u64 = 0;
for &c in &text[..int_end] {
v = v
.wrapping_mul(16)
.wrapping_add(hex_digit(c).unwrap() as u64);
}
return Some(Num::Int(v as i64));
}
let mut v: f64 = 0.0;
for &c in &text[..int_end] {
v = v * 16.0 + hex_digit(c).unwrap() as f64;
}
return Some(Num::Float(v));
}
if !float_ok {
return None;
}
let mut mant: u64 = 0;
let mut sticky = false;
let mut exp4: i64 = 0;
for &c in &text[..int_end] {
let d = hex_digit(c).unwrap() as u64;
if mant >> 60 == 0 {
mant = mant * 16 + d;
} else {
sticky |= d != 0;
exp4 += 1;
}
}
for &c in &text[frac] {
let d = hex_digit(c).unwrap() as u64;
if mant >> 60 == 0 {
mant = mant * 16 + d;
exp4 -= 1;
} else {
sticky |= d != 0;
}
}
Some(Num::Float(compose_f64(mant, sticky, exp4 * 4 + pexp)))
}
pub fn str2num(s: &[u8], int_ok: bool, hex_float_ok: bool) -> Option<Num> {
let is_space = |c: &&u8| matches!(**c, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
let mut s = s;
while s.first().filter(is_space).is_some() {
s = &s[1..];
}
while s.last().filter(is_space).is_some() {
s = &s[..s.len() - 1];
}
let neg = match s.first() {
Some(b'-') => {
s = &s[1..];
true
}
Some(b'+') => {
s = &s[1..];
false
}
_ => false,
};
let n = if s.len() > 2 && s[0] == b'0' && matches!(s[1], b'x' | b'X') {
hex_literal(&s[2..], int_ok, hex_float_ok)?
} else {
dec_literal(s, int_ok, neg)?
};
Some(if neg { n.negate() } else { n })
}
pub fn strtod_str(s: &[u8]) -> Option<f64> {
let is_space = |c: u8| matches!(c, b' ' | b'\t' | b'\n' | 0x0B | 0x0C | b'\r');
let s = &s[..s.iter().position(|&c| c == 0).unwrap_or(s.len())];
let mut i = 0;
while i < s.len() && is_space(s[i]) {
i += 1;
}
let neg = match s.get(i) {
Some(b'-') => {
i += 1;
true
}
Some(b'+') => {
i += 1;
false
}
_ => false,
};
let rest = &s[i..];
let starts = |w: &[u8]| rest.len() >= w.len() && rest[..w.len()].eq_ignore_ascii_case(w);
let (v, used) = if starts(b"infinity") {
(f64::INFINITY, 8)
} else if starts(b"inf") {
(f64::INFINITY, 3)
} else if starts(b"nan") {
let mut n = 3;
if rest.get(3) == Some(&b'(') {
let close = rest[4..]
.iter()
.position(|&c| !(c.is_ascii_alphanumeric() || c == b'_'));
if let Some(k) = close
&& rest[4 + k] == b')'
{
n = 5 + k;
}
}
(f64::NAN, n)
} else if rest.len() > 2
&& rest[0] == b'0'
&& matches!(rest[1], b'x' | b'X')
&& let Some((v, n)) = hex_prefix(&rest[2..])
{
(v, 2 + n)
} else {
dec_prefix(rest)?
};
if !rest[used..].iter().all(|&c| is_space(c)) {
return None;
}
Some(if neg { -v } else { v })
}
fn dec_prefix(s: &[u8]) -> Option<(f64, usize)> {
let digits = |s: &[u8], mut i: usize| {
while i < s.len() && s[i].is_ascii_digit() {
i += 1;
}
i
};
let int_end = digits(s, 0);
let mut i = int_end;
let mut frac = 0;
if i < s.len() && s[i] == b'.' {
let e = digits(s, i + 1);
frac = e - (i + 1);
i = e;
}
if int_end + frac == 0 {
return None;
}
if i < s.len() && matches!(s[i], b'e' | b'E') {
let mut j = i + 1;
if j < s.len() && matches!(s[j], b'+' | b'-') {
j += 1;
}
let e = digits(s, j);
if e > j {
i = e;
}
}
let text = str::from_utf8(&s[..i]).expect("ascii numeral");
Some((text.parse::<f64>().ok()?, i))
}
fn hex_prefix(s: &[u8]) -> Option<(f64, usize)> {
let hexes = |mut i: usize| {
while i < s.len() && hex_digit(s[i]).is_some() {
i += 1;
}
i
};
let int_end = hexes(0);
let mut i = int_end;
let mut frac = 0;
if i < s.len() && s[i] == b'.' {
let e = hexes(i + 1);
frac = e - (i + 1);
i = e;
}
if int_end + frac == 0 {
return None;
}
if i < s.len() && matches!(s[i], b'p' | b'P') {
let mut j = i + 1;
if j < s.len() && matches!(s[j], b'+' | b'-') {
j += 1;
}
let mut e = j;
while e < s.len() && s[e].is_ascii_digit() {
e += 1;
}
if e > j {
i = e;
}
}
hex_literal(&s[..i], false, true).map(|n| (n.as_f64(), i))
}
fn compose_f64(mant: u64, sticky: bool, exp: i64) -> f64 {
if mant == 0 {
return 0.0;
}
let bits = 64 - mant.leading_zeros() as i64;
let (m, extra) = if bits <= 53 {
(mant, 0i64)
} else {
let excess = (bits - 53) as u32;
let kept = mant >> excess;
let rem = mant & ((1u64 << excess) - 1);
let half = 1u64 << (excess - 1);
let round_up = rem > half || (rem == half && (sticky || kept & 1 == 1));
(kept + round_up as u64, excess as i64)
};
scale_f64(m as f64, exp + extra)
}
fn exp2(e: i64) -> f64 {
debug_assert!((-1022..=1023).contains(&e));
f64::from_bits(((e + 1023) as u64) << 52)
}
fn scale_f64(mut f: f64, mut e: i64) -> f64 {
while e > 1023 {
f *= exp2(1023);
e -= 1023;
if f.is_infinite() {
return f;
}
}
while e < -1022 {
f *= exp2(-1022);
e += 1022;
if f == 0.0 {
return f;
}
}
f * exp2(e)
}
pub fn num_to_string(n: Num) -> String {
num_to_string_for(n, FloatFmt::TwoStage55)
}
#[inline]
pub fn write_i64_dec(i: i64, buf: &mut [u8; 20]) -> &[u8] {
if i == 0 {
buf[0] = b'0';
return &buf[..1];
}
let neg = i < 0;
let mut n = i.unsigned_abs();
let mut pos = 20;
while n > 0 {
pos -= 1;
buf[pos] = b'0' + (n % 10) as u8;
n /= 10;
}
if neg {
pos -= 1;
buf[pos] = b'-';
}
&buf[pos..]
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatFmt {
Legacy14,
G14,
TwoStage55,
}
pub fn num_to_string_for(n: Num, fmt: FloatFmt) -> String {
match n {
Num::Int(i) => i.to_string(),
Num::Float(f) => float_to_string(f, fmt),
}
}
fn format_g(f: f64, prec: usize) -> String {
debug_assert!(prec >= 1);
let sci = format!("{f:.*e}", prec - 1);
let epos = sci.rfind('e').expect("scientific form has exponent");
let exp: i32 = sci[epos + 1..].parse().expect("valid exponent");
if exp < -4 || exp >= prec as i32 {
let mut mant = sci[..epos].to_string();
if mant.contains('.') {
while mant.ends_with('0') {
mant.pop();
}
if mant.ends_with('.') {
mant.pop();
}
}
let (esign, eabs) = if exp < 0 { ('-', -exp) } else { ('+', exp) };
format!("{mant}e{esign}{eabs:02}")
} else {
let decimals = (prec as i32 - 1 - exp).max(0) as usize;
let mut s = format!("{f:.decimals$}");
if s.contains('.') {
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
}
s
}
}
pub(crate) fn nan_spelling(f: f64) -> (bool, &'static str) {
let negative = f.is_sign_negative();
if cfg!(target_vendor = "apple") {
(false, "nan")
} else if cfg!(target_os = "windows") {
let bits = f.to_bits();
let quiet = bits & (1 << 51) != 0;
let body = if !quiet {
"nan(snan)"
} else if bits == 0xFFF8_0000_0000_0000 {
"nan(ind)"
} else {
"nan"
};
(negative, body)
} else {
(negative, "nan")
}
}
fn float_to_string(f: f64, fmt: FloatFmt) -> String {
if f.is_nan() {
let (negative, body) = nan_spelling(f);
return if negative {
format!("-{body}")
} else {
body.to_string()
};
}
if f.is_infinite() {
return if f < 0.0 { "-inf" } else { "inf" }.to_string();
}
let mut s = match fmt {
FloatFmt::Legacy14 | FloatFmt::G14 => format_g(f, 14),
FloatFmt::TwoStage55 => {
let first = format_g(f, 15);
if first.parse::<f64>() == Ok(f) {
first
} else {
format_g(f, 17)
}
}
};
if s.bytes().all(|c| c.is_ascii_digit() || c == b'-') && fmt != FloatFmt::Legacy14 {
s.push_str(".0");
}
s
}
#[cfg(test)]
mod tests {
#[test]
fn strtod_str_reads_what_c_strtod_reads() {
assert_eq!(strtod_str(b" 10 "), Some(10.0));
assert_eq!(strtod_str(b"10\0zz"), Some(10.0));
assert_eq!(strtod_str(b"-inf"), Some(f64::NEG_INFINITY));
assert_eq!(strtod_str(b"Infinity"), Some(f64::INFINITY));
assert!(strtod_str(b"nan").is_some_and(f64::is_nan));
assert!(strtod_str(b"nan(123)").is_some_and(f64::is_nan));
assert_eq!(strtod_str(b"0x1p4"), Some(16.0));
assert_eq!(strtod_str(b"0x.8"), Some(0.5));
assert_eq!(strtod_str(b".5e1"), Some(5.0));
assert_eq!(strtod_str(b"0x"), None);
assert_eq!(strtod_str(b"1e"), None);
assert_eq!(strtod_str(b"infx"), None);
assert_eq!(strtod_str(b""), None);
}
use super::*;
#[test]
fn str2num_semantics() {
assert_eq!(str2num(b" 42 ", true, true), Some(Num::Int(42)));
assert_eq!(str2num(b"-10", true, true), Some(Num::Int(-10)));
assert_eq!(str2num(b"+0x10", true, true), Some(Num::Int(16)));
assert_eq!(str2num(b"-0x10", true, true), Some(Num::Int(-16)));
assert_eq!(str2num(b" 0x1p4 ", true, true), Some(Num::Float(16.0)));
assert_eq!(str2num(b"3.5", true, true), Some(Num::Float(3.5)));
assert_eq!(str2num(b"1e3", true, true), Some(Num::Float(1000.0)));
assert_eq!(str2num(b"", true, true), None);
assert_eq!(str2num(b" - 1", true, true), None);
assert_eq!(str2num(b"10a", true, true), None);
assert_eq!(str2num(b"0x", true, true), None);
assert_eq!(str2num(b"42", false, false), Some(Num::Float(42.0)));
assert_eq!(str2num(b"0x1p4", false, false), None);
assert_eq!(
str2num(b"-9223372036854775808", true, true),
Some(Num::Int(i64::MIN))
);
assert_eq!(
str2num(b"9223372036854775807", true, true),
Some(Num::Int(i64::MAX))
);
assert_eq!(
str2num(b"9223372036854775808", true, true),
Some(Num::Float(9223372036854775808.0))
);
}
#[test]
fn number_printing() {
assert_eq!(num_to_string(Num::Int(42)), "42");
assert_eq!(num_to_string(Num::Int(-1)), "-1");
assert_eq!(num_to_string(Num::Float(2.0)), "2.0");
assert_eq!(num_to_string(Num::Float(-2.0)), "-2.0");
assert_eq!(num_to_string(Num::Float(0.5)), "0.5");
assert_eq!(num_to_string(Num::Float(1e300)), "1e+300");
assert_eq!(num_to_string(Num::Float(1e-7)), "1e-07");
assert_eq!(num_to_string(Num::Float(1e15)), "1e+15");
assert_eq!(num_to_string(Num::Float(100.0)), "100.0");
assert_eq!(num_to_string(Num::Float(f64::INFINITY)), "inf");
assert_eq!(num_to_string(Num::Float(f64::NAN)), "nan");
assert_eq!(num_to_string(Num::Float(0.1)), "0.1");
assert_eq!(num_to_string(Num::Float(1.0 / 3.0)), "0.33333333333333331");
assert_eq!(
num_to_string(Num::Float(std::f64::consts::PI)),
"3.1415926535897931"
);
assert_eq!(num_to_string(Num::Float(1e14)), "100000000000000.0");
assert_eq!(
num_to_string(Num::Float(9007199254740992.0)),
"9007199254740992.0"
);
assert_eq!(num_to_string(Num::Float(5e-324)), "4.94065645841247e-324");
}
#[test]
fn hex_float_rounding() {
let Some(Num::Float(f)) = hex_literal(b"1FFFFFFFFFFFFF8.0p0", true, true) else {
panic!()
};
assert_eq!(f, 0x1FFFFFFFFFFFFF8u64 as f64);
let Some(Num::Float(g)) = hex_literal(b"1.8p1", true, true) else {
panic!()
};
assert_eq!(g, 3.0);
let Some(Num::Float(h)) = hex_literal(b"0.8", true, true) else {
panic!()
};
assert_eq!(h, 0.5);
}
}