use std::fmt;
const POW10: [u128; 23] = {
let mut t = [1u128; 23];
let mut i = 1;
while i < 23 {
t[i] = t[i - 1] * 10;
i += 1;
}
t
};
const MAX_FAST_PRECISION: usize = POW10.len() - 1;
pub fn write_shortest<W: fmt::Write>(f: f64, out: &mut W) -> fmt::Result {
if f.is_nan() {
return out.write_str("NaN");
}
if f.is_infinite() {
return out.write_str(if f.is_sign_negative() { "-inf" } else { "inf" });
}
let mut buf = ryu::Buffer::new();
let text = buf.format_finite(f);
if shortest_is_tie(f, text) {
return write!(out, "{f:?}");
}
let (sign, body) = match text.strip_prefix('-') {
Some(rest) => ("-", rest),
None => ("", text),
};
let Some(digits) = body.strip_prefix("0.0000") else {
return out.write_str(text);
};
out.write_str(sign)?;
out.write_str(&digits[..1])?;
if digits.len() > 1 {
out.write_char('.')?;
out.write_str(&digits[1..])?;
}
out.write_str("e-5")
}
#[inline]
fn shortest_is_tie(f: f64, text: &str) -> bool {
let bits = f.to_bits();
let exp_bits = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & ((1u64 << 52) - 1);
let (m, e) = if exp_bits == 0 {
(frac, -1074)
} else {
(frac | (1u64 << 52), exp_bits - 1075)
};
let tz = m.trailing_zeros() as i32;
let e = e + tz;
if e >= 0 {
return false;
}
let j = (-e) as u32;
if j > 26 {
return false;
}
let x = (m >> tz) as u128 * 5u128.pow(j);
if x >= POW10[18] {
return false;
}
let n = significant_digits(text);
POW10[n] <= x && x < POW10[n + 1]
}
#[inline]
fn significant_digits(text: &str) -> usize {
let bytes = text.as_bytes();
let end = bytes.iter().position(|&b| b == b'e').unwrap_or(bytes.len());
let mantissa = &bytes[..end];
let Some(first) = mantissa.iter().position(|b| (b'1'..=b'9').contains(b)) else {
return 0;
};
let last = mantissa
.iter()
.rposition(|b| (b'1'..=b'9').contains(b))
.expect("a first nonzero digit implies a last");
mantissa[first..=last]
.iter()
.filter(|b| b.is_ascii_digit())
.count()
}
pub fn shortest_is_fast(f: f64) -> bool {
if !f.is_finite() || f == 0.0 {
return true;
}
let mut buf = ryu::Buffer::new();
!shortest_is_tie(f, buf.format_finite(f))
}
pub fn write_fixed<W: fmt::Write>(f: f64, prec: usize, out: &mut W) -> fmt::Result {
if f.is_nan() {
return out.write_str("NaN");
}
if f.is_infinite() {
return out.write_str(if f.is_sign_negative() { "-inf" } else { "inf" });
}
let Some(q) = fixed_scaled(f, prec) else {
return write!(out, "{f:.prec$}");
};
let mut buf = [0u8; 48];
let len = layout_fixed(f.is_sign_negative(), q, prec, &mut buf);
out.write_str(std::str::from_utf8(&buf[len..]).expect("ascii"))
}
#[inline]
fn fixed_scaled(f: f64, prec: usize) -> Option<u128> {
if prec > MAX_FAST_PRECISION {
return None;
}
let bits = f.to_bits();
let exp_bits = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & ((1u64 << 52) - 1);
let (m, e) = if exp_bits == 0 {
(frac, -1074)
} else {
(frac | (1u64 << 52), exp_bits - 1075)
};
if m == 0 {
return Some(0);
}
let t = m as u128 * POW10[prec];
if e >= 0 {
if e as u32 > t.leading_zeros() {
return None;
}
return Some(t << e);
}
let s = (-e) as u32;
if s >= 128 {
return Some(0);
}
let q = t >> s;
let rem = t & ((1u128 << s) - 1);
let half = 1u128 << (s - 1);
Some(if rem > half || (rem == half && q & 1 == 1) {
q + 1
} else {
q
})
}
#[inline]
fn layout_fixed(negative: bool, q: u128, prec: usize, buf: &mut [u8; 48]) -> usize {
macro_rules! put_scaled {
($q:expr, $scale:expr) => {{
let mut i = buf.len();
let mut q = $q;
let mut frac = q % $scale;
q /= $scale;
for _ in 0..prec {
i -= 1;
buf[i] = b'0' + (frac % 10) as u8;
frac /= 10;
}
if prec > 0 {
i -= 1;
buf[i] = b'.';
}
loop {
i -= 1;
buf[i] = b'0' + (q % 10) as u8;
q /= 10;
if q == 0 {
break;
}
}
i
}};
}
let mut i = if q <= u64::MAX as u128 {
put_scaled!(q as u64, POW10[prec] as u64)
} else {
put_scaled!(q, POW10[prec])
};
if negative {
i -= 1;
buf[i] = b'-';
}
i
}
pub fn shortest_string(f: f64) -> String {
let mut s = String::with_capacity(24);
let _ = write_shortest(f, &mut s);
s
}
pub fn fixed_string(f: f64, prec: usize) -> String {
let mut s = String::with_capacity(24 + prec);
let _ = write_fixed(f, prec, &mut s);
s
}
pub fn fixed_is_fast(f: f64, prec: usize) -> bool {
f.is_finite() && fixed_scaled(f, prec).is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shortest_matches_debug_on_named_values() {
for f in [
0.0,
-0.0,
1.0,
100.0,
0.1,
1e-5,
5e-5,
9.99e-5,
1e-4,
1e15,
1e16,
1.5e300,
5e-324,
f64::MAX,
f64::MIN,
f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
-0.000012345,
] {
assert_eq!(
shortest_string(f),
format!("{f:?}"),
"{:#018x}",
f.to_bits()
);
}
}
#[test]
fn fixed_matches_format_on_named_values() {
for f in [
0.0,
-0.0,
0.5,
1.5,
2.5,
0.295,
4.35,
2.675,
1.005,
1e21,
1e29,
1e30,
1e300,
5e-324,
-1e-30,
123_456_789.123_456_79,
] {
for prec in 0..=9 {
assert_eq!(
fixed_string(f, prec),
format!("{f:.prec$}"),
"{:#018x} .{prec}",
f.to_bits()
);
}
}
assert!(!fixed_is_fast(1e300, 2));
assert!(fixed_is_fast(1e21, 9));
assert!(fixed_is_fast(5e-324, 9));
}
}