use super::chars::{is_digit, trim, trim_start};
pub(crate) fn to_string(f: f64) -> String {
if f.is_nan() {
return "NaN".into();
}
if f == 0.0 {
return "0".into();
}
if f.is_infinite() {
return if f > 0.0 { "Infinity" } else { "-Infinity" }.into();
}
if f < 0.0 {
return format!("-{}", to_string(-f));
}
if f < 9007199254740992.0 && f == f.trunc() {
return format!("{}", f as u64);
}
let (digits, x) = shortest(f);
let k = digits.len() as i32;
let n = x + 1;
if k <= n && n <= 21 {
return digits + &"0".repeat((n - k) as usize);
}
if 0 < n && n <= 21 {
let (a, b) = digits.split_at(n as usize);
return format!("{a}.{b}");
}
if -6 < n && n <= 0 {
return format!("0.{}{digits}", "0".repeat((-n) as usize));
}
let e10 = n - 1;
let sign = if e10 < 0 { '-' } else { '+' };
if k == 1 {
format!("{digits}e{sign}{}", e10.abs())
} else {
format!("{}.{}e{sign}{}", &digits[..1], &digits[1..], e10.abs())
}
}
fn shortest(f: f64) -> (String, i32) {
let e = format!("{f:e}");
let (mant, exp) = e.split_once('e').unwrap_or((&e, "0"));
let mut digits = mant.replace('.', "");
let x: i32 = exp.parse().unwrap_or(0);
let k = digits.len();
let exact = Exact::of(f);
let first = exact.digits.len() as i32 - 1 - exact.point as i32;
if exact.digits.len() == k + 1 && exact.digits[k] == 5 && first == x {
let mut even: Vec<u8> = exact.digits[..k].to_vec();
if even[k - 1] % 2 == 1 && even[k - 1] < 9 {
even[k - 1] += 1;
}
let even: String = even.iter().map(|&c| char::from(b'0' + c)).collect();
if even != digits && (even.as_bytes()[k - 1] - b'0').is_multiple_of(2) {
let back: f64 = format!("{even}e{}", x - (k as i32 - 1))
.parse()
.unwrap_or(0.0);
if back == f {
digits = even;
}
}
}
(digits, x)
}
pub(crate) fn to_fixed(x: f64, digits: usize) -> String {
if x.is_nan() {
return "NaN".into();
}
if x.abs() >= 1e21 {
return to_string(x);
}
let sign = if x < 0.0 { "-" } else { "" };
let exact = Exact::of(x.abs());
let mut s = exact.rounded(exact.digits.len() as isize - exact.point as isize + digits as isize);
if digits > 0 {
if s.len() <= digits {
s = "0".repeat(digits + 1 - s.len()) + &s;
}
s.insert(s.len() - digits, '.');
}
format!("{sign}{s}")
}
pub(crate) fn to_exponential(x: f64, digits: usize) -> String {
let (sign, mut m, e) = match significand(x, digits + 1) {
Ok(s) => s,
Err(word) => return word,
};
if digits > 0 {
m.insert(1, '.');
}
format!("{sign}{m}{}", exponent(e))
}
pub(crate) fn to_precision(x: f64, precision: usize) -> String {
let (sign, mut m, e) = match significand(x, precision) {
Ok(s) => s,
Err(word) => return word,
};
let p = precision as i32;
if e < -6 || e >= p {
if precision > 1 {
m.insert(1, '.');
}
return format!("{sign}{m}{}", exponent(e));
}
if e == p - 1 {
return format!("{sign}{m}");
}
if e >= 0 {
m.insert((e + 1) as usize, '.');
return format!("{sign}{m}");
}
format!("{sign}0.{}{m}", "0".repeat((-(e + 1)) as usize))
}
fn significand(x: f64, n: usize) -> Result<(&'static str, String, i32), String> {
if x.is_nan() {
return Err("NaN".into());
}
let sign = if x < 0.0 { "-" } else { "" };
let x = x.abs();
if x.is_infinite() {
return Err(format!("{sign}Infinity"));
}
if x == 0.0 {
return Ok((sign, "0".repeat(n), 0));
}
let exact = Exact::of(x);
let mut e = exact.digits.len() as i32 - 1 - exact.point as i32;
let mut digits = exact.rounded(n as isize);
if digits.len() > n {
e += 1;
digits.truncate(n);
}
Ok((sign, digits, e))
}
fn exponent(e: i32) -> String {
if e < 0 {
format!("e-{}", -e)
} else {
format!("e+{e}")
}
}
struct Exact {
digits: Vec<u8>,
point: usize,
}
impl Exact {
fn of(x: f64) -> Exact {
let bits = x.to_bits();
let exp = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & ((1 << 52) - 1);
let (m, e) = if exp == 0 {
(frac, -1074)
} else {
(frac | (1 << 52), exp - 1075)
};
let mut n = Big::from(m);
let point = if e >= 0 {
n.shl(e as u32);
0
} else {
let (thirteens, rest) = (-e / 13, -e % 13);
for _ in 0..thirteens {
n.mul_small(5u32.pow(13));
}
n.mul_small(5u32.pow(rest as u32));
(-e) as usize
};
let mut digits = n.decimal();
let mut point = point;
while point > 0 && digits.last() == Some(&0) {
digits.pop();
point -= 1;
}
Exact { digits, point }
}
fn rounded(&self, k: isize) -> String {
let d = &self.digits;
if k <= 0 {
let up = k == 0 && d.first().is_some_and(|&c| c >= 5);
return if up { "1" } else { "0" }.into();
}
let k = k as usize;
let mut out: Vec<u8> = d.iter().copied().take(k).collect();
out.resize(k, 0);
if d.get(k).is_some_and(|&c| c >= 5) {
let mut i = out.len();
loop {
if i == 0 {
out.insert(0, 1);
break;
}
i -= 1;
if out[i] == 9 {
out[i] = 0;
} else {
out[i] += 1;
break;
}
}
}
let first = out.iter().position(|&c| c != 0).unwrap_or(out.len() - 1);
out[first..].iter().map(|&c| char::from(b'0' + c)).collect()
}
}
struct Big(Vec<u32>);
impl Big {
fn from(n: u64) -> Big {
Big(vec![n as u32, (n >> 32) as u32])
}
fn mul_small(&mut self, k: u32) {
let mut carry = 0u64;
for d in &mut self.0 {
let v = u64::from(*d) * u64::from(k) + carry;
*d = v as u32;
carry = v >> 32;
}
if carry > 0 {
self.0.push(carry as u32);
}
}
fn shl(&mut self, bits: u32) {
let (words, bits) = ((bits / 32) as usize, bits % 32);
if bits > 0 {
let mut carry = 0u32;
for d in &mut self.0 {
let v = (*d << bits) | carry;
carry = *d >> (32 - bits);
*d = v;
}
if carry > 0 {
self.0.push(carry);
}
}
self.0.splice(0..0, std::iter::repeat_n(0, words));
}
fn is_zero(&self) -> bool {
self.0.iter().all(|&d| d == 0)
}
fn div_small(&mut self, k: u32) -> u32 {
let mut rem = 0u64;
for d in self.0.iter_mut().rev() {
let v = (rem << 32) | u64::from(*d);
*d = (v / u64::from(k)) as u32;
rem = v % u64::from(k);
}
rem as u32
}
fn decimal(mut self) -> Vec<u8> {
let mut chunks = Vec::new();
while !self.is_zero() {
chunks.push(self.div_small(1_000_000_000));
}
let mut out = Vec::with_capacity(chunks.len() * 9);
for (i, c) in chunks.iter().rev().enumerate() {
let s = if i == 0 {
c.to_string()
} else {
format!("{c:09}")
};
out.extend(s.bytes().map(|b| b - b'0'));
}
if out.is_empty() {
out.push(0);
}
out
}
fn to_f64(&self) -> f64 {
let mut words = self.0.clone();
while words.last() == Some(&0) {
words.pop();
}
let Some(&top) = words.last() else { return 0.0 };
let bits = (words.len() as u32 - 1) * 32 + (32 - top.leading_zeros());
if bits <= 64 {
let v = words
.iter()
.rev()
.fold(0u64, |a, &w| (a << 32) | u64::from(w));
return v as f64;
}
let shift = bits - 64;
let bit = |i: u32| (words[(i / 32) as usize] >> (i % 32)) & 1;
let mut v = 0u64;
for i in (shift..bits).rev() {
v = (v << 1) | u64::from(bit(i));
}
let sticky = (0..shift).any(|i| bit(i) == 1);
let (keep, rest) = (v >> 11, v & 0x7ff);
let up = rest > 0x400 || (rest == 0x400 && (sticky || keep & 1 == 1));
let m = keep + u64::from(up);
(m as f64) * 2f64.powi((shift + 11) as i32)
}
}
pub(crate) fn number(s: &str) -> f64 {
let s = trim(s);
if s.is_empty() {
return 0.0;
}
let b = s.as_bytes();
if b.len() > 2 && b[0] == b'0' {
let base = match b[1] {
b'x' | b'X' => 16,
b'o' | b'O' => 8,
b'b' | b'B' => 2,
_ => 0,
};
if base != 0 {
return digits_value(&s[2..], base, false);
}
}
let body = s.strip_prefix(['+', '-']).unwrap_or(s);
if body == "Infinity" {
return if b[0] == b'-' {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
if !is_decimal(body) {
return f64::NAN;
}
s.parse().unwrap_or(f64::NAN)
}
fn is_decimal(s: &str) -> bool {
let b = s.as_bytes();
let (mut i, mut digits) = (0, 0);
let run = |i: &mut usize| {
let from = *i;
while *i < b.len() && is_digit(i32::from(b[*i])) {
*i += 1;
}
*i - from
};
digits += run(&mut i);
if i < b.len() && b[i] == b'.' {
i += 1;
digits += run(&mut i);
}
if digits == 0 {
return false;
}
if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
i += 1;
if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
i += 1;
}
if run(&mut i) == 0 {
return false;
}
}
i == b.len()
}
fn digits_value(s: &str, base: u32, prefix: bool) -> f64 {
let end = s
.bytes()
.position(|c| char::from(c).to_digit(36).is_none_or(|d| d >= base))
.unwrap_or(s.len());
if end == 0 || !prefix && end != s.len() {
return f64::NAN;
}
let mut n = Big::from(0);
for c in s[..end].chars() {
n.mul_small(base);
let d = c.to_digit(base).unwrap_or(0);
let mut carry = u64::from(d);
for w in &mut n.0 {
let v = u64::from(*w) + carry;
*w = v as u32;
carry = v >> 32;
}
if carry > 0 {
n.0.push(carry as u32);
}
}
n.to_f64()
}
pub(crate) fn parse_int(s: &str, radix: u32) -> f64 {
let mut s = trim_start(s);
let mut neg = false;
if let Some(rest) = s.strip_prefix(['+', '-']) {
neg = s.starts_with('-');
s = rest;
}
if radix == 16 && (s.starts_with("0x") || s.starts_with("0X")) {
s = &s[2..];
}
let f = if radix == 10 {
let end = s
.bytes()
.position(|c| !c.is_ascii_digit())
.unwrap_or(s.len());
if end == 0 {
return f64::NAN;
}
s[..end].parse().unwrap_or(f64::NAN)
} else {
digits_value(s, radix, true)
};
if neg { -f } else { f }
}
pub(crate) fn parse_float(s: &str) -> f64 {
let s = trim_start(s);
let b = s.as_bytes();
let mut i = usize::from(s.starts_with(['+', '-']));
if s[i..].starts_with("Infinity") {
return if s.starts_with('-') {
f64::NEG_INFINITY
} else {
f64::INFINITY
};
}
let run = |from: usize| {
let mut end = from;
while end < b.len() && b[end].is_ascii_digit() {
end += 1;
}
end
};
let whole = run(i);
let mut digits = whole > i;
i = whole;
if b.get(i) == Some(&b'.') {
let fraction = run(i + 1);
if fraction > i + 1 || digits {
(i, digits) = (fraction, true);
}
}
if !digits {
return f64::NAN;
}
if matches!(b.get(i), Some(b'e' | b'E')) {
let mut j = i + 1;
if matches!(b.get(j), Some(b'+' | b'-')) {
j += 1;
}
let k = run(j);
if k > j {
i = k;
}
}
s[..i].parse().unwrap_or(f64::NAN)
}