const MAX_SAFE_INTEGER: u64 = (1 << 53) - 1;
const DIGIT_PAIRS: &[u8; 200] = b"\
0001020304050607080910111213141516171819\
2021222324252627282930313233343536373839\
4041424344454647484950515253545556575859\
6061626364656667686970717273747576777879\
8081828384858687888990919293949596979899";
fn decimal(buf: &mut [u8; 20], mut n: u64) -> usize {
let mut i = buf.len();
while n >= 100 {
let pair = (n % 100) as usize * 2;
n /= 100;
i -= 2;
buf[i..i + 2].copy_from_slice(&DIGIT_PAIRS[pair..pair + 2]);
}
if n >= 10 {
let pair = n as usize * 2;
i -= 2;
buf[i..i + 2].copy_from_slice(&DIGIT_PAIRS[pair..pair + 2]);
} else {
i -= 1;
buf[i] = b'0' + n as u8;
}
i
}
fn push_decimal(out: &mut String, n: u64) {
if n < 10 {
out.push(char::from(b'0' + n as u8));
return;
}
let mut buf = [0; 20];
let start = decimal(&mut buf, n);
out.reserve(buf.len() - start);
for &b in &buf[start..] {
out.push(char::from(b));
}
}
pub fn push_int(out: &mut String, n: i64) {
if n.unsigned_abs() <= MAX_SAFE_INTEGER {
if n < 0 {
out.push('-');
}
push_decimal(out, n.unsigned_abs());
} else {
push_number(out, n as f64);
}
}
struct Short {
buf: [u8; 48],
len: usize,
}
impl Short {
const fn new() -> Short {
Short {
buf: [0; 48],
len: 0,
}
}
fn of(digits: &str) -> Short {
let mut short = Short::new();
short.extend(digits.bytes());
short
}
fn extend(&mut self, bytes: impl Iterator<Item = u8>) {
for b in bytes {
self.buf[self.len] = b;
self.len += 1;
}
}
fn as_str(&self) -> &str {
std::str::from_utf8(&self.buf[..self.len]).expect("only ASCII is written")
}
}
impl std::fmt::Write for Short {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let end = self.len + s.len();
self.buf
.get_mut(self.len..end)
.ok_or(std::fmt::Error)?
.copy_from_slice(s.as_bytes());
self.len = end;
Ok(())
}
}
pub fn push_number(out: &mut String, x: f64) {
use std::fmt::Write;
if x.is_nan() {
out.push_str("NaN");
return;
}
if x == 0.0 {
out.push('0');
return;
}
if x.is_infinite() {
out.push_str(if x < 0.0 { "-Infinity" } else { "Infinity" });
return;
}
if x < 0.0 {
out.push('-');
}
let x = x.abs();
if x.fract() == 0.0 && x <= MAX_SAFE_INTEGER as f64 {
push_decimal(out, x as u64);
return;
}
let mut sci = Short::new();
let _ = write!(sci, "{x:e}");
let (mantissa, exp) = sci
.as_str()
.split_once('e')
.expect("`{:e}` writes an exponent");
let exp: i32 = exp.parse().expect("`{:e}` writes an integer exponent");
let mut digits = Short::new();
digits.extend(mantissa.bytes().filter(|b| *b != b'.'));
if let Some((exact, exact_exp)) = few_exact_digits(x)
&& exact_exp == exp
{
let exact = exact.as_str();
let k = digits.len;
if exact.len() == k + 1 && exact.ends_with('5') {
let lower = &exact[..k];
let upper = increment_digits(lower);
let even = |d: &str| {
d.bytes()
.last()
.is_some_and(|b| (b - b'0').is_multiple_of(2))
};
if upper.len() == k && (digits.as_str() == lower || digits.as_str() == upper) {
digits = Short::of(if even(lower) { lower } else { &upper });
}
}
}
let digits = digits.as_str();
let k = digits.len() as i32;
let n = exp + 1;
if k <= n && n <= 21 {
out.push_str(digits);
out.extend(std::iter::repeat_n('0', (n - k) as usize));
} else if 0 < n && n <= 21 {
out.push_str(&digits[..n as usize]);
out.push('.');
out.push_str(&digits[n as usize..]);
} else if -6 < n && n <= 0 {
out.push_str("0.");
out.extend(std::iter::repeat_n('0', (-n) as usize));
out.push_str(digits);
} else {
out.push_str(&digits[..1]);
if k > 1 {
out.push('.');
out.push_str(&digits[1..]);
}
let e = n - 1;
let _ = write!(out, "e{}{}", if e < 0 { '-' } else { '+' }, e.abs());
}
}
fn few_exact_digits(x: f64) -> Option<(Short, i32)> {
use std::fmt::Write;
let bits = x.to_bits();
let biased = ((bits >> 52) & 0x7ff) as i32;
let fraction = bits & ((1 << 52) - 1);
let (m, e) = if biased == 0 {
(fraction, -1074)
} else {
(fraction | 1 << 52, biased - 1075)
};
let shift = m.trailing_zeros();
let (m, e) = (m >> shift, e + shift as i32);
let (value, scale) = if e < 0 {
if -e >= 26 {
return None;
}
(u128::from(m) * 5u128.pow(e.unsigned_abs()), e)
} else if e <= 74 {
(u128::from(m) << e, 0)
} else if e < 82 {
let mut s = String::new();
let _ = write!(s, "{x:.40e}");
let (mantissa, exp) = s.split_once('e')?;
let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
let digits = digits.trim_end_matches('0');
let exp = exp.parse().ok()?;
return (digits.len() <= 18).then(|| (Short::of(digits), exp));
} else {
return None;
};
let mut digits = Short::new();
let _ = write!(digits, "{value}");
let all = digits.len;
digits.len = digits.as_str().trim_end_matches('0').len();
(digits.len <= 18).then_some((digits, all as i32 - 1 + scale))
}
#[must_use]
pub fn js_round(x: f64) -> f64 {
let f = x.floor();
let r = if x - f >= 0.5 { f + 1.0 } else { f };
if r == 0.0 && x.is_sign_negative() {
-0.0
} else {
r
}
}
#[must_use]
pub fn js_max(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a > b || (a == b && b.is_sign_negative()) {
a
} else {
b
}
}
#[must_use]
pub fn js_min(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a < b || (a == b && a.is_sign_negative()) {
a
} else {
b
}
}
#[must_use]
pub fn js_to_fixed(x: f64, digits: u32) -> String {
use std::fmt::Write;
if x.is_nan() {
return "NaN".to_owned();
}
if x.abs() >= 1e21 || x.is_infinite() {
let mut s = String::new();
push_number(&mut s, x);
return s;
}
let d = digits as usize;
let mut out = String::new();
let _ = write!(out, "{:.*}", d, x.abs());
let mut exact = String::new();
let _ = write!(exact, "{:.1100}", x.abs());
let exact = exact.trim_end_matches('0');
let fraction = exact.split_once('.').map_or("", |(_, f)| f);
if fraction.len() == d + 1 && fraction.ends_with('5') {
out = round_up_magnitude(&exact[..exact.len() - 1]);
}
if x < 0.0 {
out.insert(0, '-');
}
out
}
fn round_up_magnitude(digits: &str) -> String {
let digits = digits.trim_end_matches('.');
let mut bytes: Vec<u8> = digits.bytes().collect();
let mut i = bytes.len();
loop {
if i == 0 {
bytes.insert(0, b'1');
break;
}
i -= 1;
match bytes[i] {
b'.' => continue,
b'9' => bytes[i] = b'0',
d => {
bytes[i] = d + 1;
break;
}
}
}
String::from_utf8(bytes).expect("ASCII digits")
}
fn increment_digits(digits: &str) -> String {
let mut bytes: Vec<u8> = digits.bytes().collect();
for b in bytes.iter_mut().rev() {
if *b == b'9' {
*b = b'0';
} else {
*b += 1;
return String::from_utf8(bytes).expect("ASCII digits");
}
}
bytes.insert(0, b'1');
String::from_utf8(bytes).expect("ASCII digits")
}
#[derive(Debug, Clone, Copy)]
pub struct Js<T>(pub T);
impl std::fmt::Display for Js<i64> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.unsigned_abs() > MAX_SAFE_INTEGER {
return Js(self.0 as f64).fmt(f);
}
let mut buf = [0; 20];
let start = decimal(&mut buf, self.0.unsigned_abs());
if self.0 < 0 {
f.write_str("-")?;
}
f.write_str(std::str::from_utf8(&buf[start..]).expect("ASCII digits"))
}
}
impl std::fmt::Display for Js<f64> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = String::new();
push_number(&mut s, self.0);
f.write_str(&s)
}
}
#[cfg(test)]
mod tests;