pub fn cvt_double_to_string(v: f64, precision: u16) -> String {
const FRAC_MULTIPLIER: [i64; 9] = [
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
];
if v.is_nan() || precision > 8 || !(-10_000_000.0..=10_000_000.0).contains(&v) {
if precision > 8 || v > 1e16 || v < -1e16 {
let p = precision.min(17) as usize;
return pad_start(&fmt_e(v, p, false), p + 7);
}
return fmt_f(v, precision.min(3) as usize);
}
let precision = precision as usize;
let mut out = String::new();
let mut value = v;
if value < 0.0 {
out.push('-');
value = -value;
}
let mut whole = value as i64;
let ftemp = value - whole as f64;
let fplace = FRAC_MULTIPLIER[precision];
let mut fraction = (ftemp * fplace as f64 * 10.0) as i64;
fraction = (fraction + 5) / 10;
if fraction / fplace >= 1 {
whole += 1;
fraction -= fplace;
}
let mut got_one = false;
let mut iplace = 10_000_000i64;
while iplace >= 1 {
if whole >= iplace {
got_one = true;
let number = whole / iplace;
whole -= number * iplace;
out.push((b'0' + number as u8) as char);
} else if got_one {
out.push('0');
}
iplace /= 10;
}
if !got_one {
out.push('0');
}
if precision > 0 {
out.push('.');
let mut fplace = fplace / 10;
for _ in 0..precision {
let number = fraction / fplace;
fraction -= number * fplace;
out.push((b'0' + number as u8) as char);
fplace /= 10;
}
}
out
}
pub fn to_string(v: f64) -> String {
if v.is_nan() {
return "NaN".to_string();
}
cvt_double_to_string(v, 8)
}
pub fn fmt_f(v: f64, precision: usize) -> String {
if !v.is_finite() {
return non_finite(v, false);
}
format!("{v:.precision$}")
}
pub fn fmt_g(v: f64, precision: usize, upper: bool, alt: bool) -> String {
if !v.is_finite() {
return non_finite(v, upper);
}
let p = precision.max(1);
let probe = format!("{v:.*e}", p - 1);
let exp: i32 = probe
.split_once('e')
.expect("Rust LowerExp always emits `e`")
.1
.parse()
.expect("Rust LowerExp always emits an integer");
let rendered = if exp < -4 || exp >= p as i32 {
fmt_e(v, p - 1, upper)
} else {
fmt_f(v, (p as i32 - 1 - exp).max(0) as usize)
};
if alt {
return rendered;
}
strip_trailing_zeros(&rendered)
}
fn strip_trailing_zeros(s: &str) -> String {
let (num, suffix) = match s.find(['e', 'E']) {
Some(i) => (&s[..i], &s[i..]),
None => (s, ""),
};
if !num.contains('.') {
return s.to_string();
}
let trimmed = num.trim_end_matches('0').trim_end_matches('.');
format!("{trimmed}{suffix}")
}
pub fn fmt_e(v: f64, precision: usize, upper: bool) -> String {
if !v.is_finite() {
return non_finite(v, upper);
}
let s = format!("{v:.precision$e}");
let (mantissa, exp) = s.split_once('e').expect("Rust LowerExp always emits `e`");
let exp: i32 = exp.parse().expect("Rust LowerExp always emits an integer");
let e = if upper { 'E' } else { 'e' };
let sign = if exp < 0 { '-' } else { '+' };
format!("{mantissa}{e}{sign}{:02}", exp.abs())
}
fn non_finite(v: f64, upper: bool) -> String {
let s = if v.is_nan() {
"nan"
} else if v > 0.0 {
"inf"
} else {
"-inf"
};
if upper {
s.to_uppercase()
} else {
s.to_string()
}
}
fn pad_start(s: &str, width: usize) -> String {
if s.len() >= width {
return s.to_string();
}
format!("{}{s}", " ".repeat(width - s.len()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_compiled_cvt_double_to_string_at_precision_8() {
let cases: &[(f64, &str)] = &[
(std::f64::consts::PI, "3.14159265"),
(0.0, "0.00000000"),
(-0.0, "0.00000000"),
(1.0, "1.00000000"),
(-1.0, "-1.00000000"),
(42.0, "42.00000000"),
(0.5, "0.50000000"),
(-0.5, "-0.50000000"),
(1e-9, "0.00000000"),
(1e-8, "0.00000001"),
(5e-9, "0.00000001"),
(-5e-9, "-0.00000001"),
(1.23456789, "1.23456789"),
(1.999999995, "2.00000000"),
(9999999.99999999, "9999999.99999999"),
(10000000.0, "10000000.00000000"),
(10000000.5, "10000000.500"),
(1.0000001e7, "10000001.000"),
(1e8, "100000000.000"),
(-1e8, "-100000000.000"),
(1e10, "10000000000.000"),
(123456789.125, "123456789.125"),
(1e15, "1000000000000000.000"),
(1e16, "10000000000000000.000"),
(1.0000001e16, " 1.00000010e+16"),
(1e17, " 1.00000000e+17"),
(-1e17, "-1.00000000e+17"),
(1e300, "1.00000000e+300"),
(-1e300, "-1.00000000e+300"),
(f64::INFINITY, " inf"),
(f64::NEG_INFINITY, " -inf"),
(1e-300, "0.00000000"),
(-1e-300, "-0.00000000"),
(2.5, "2.50000000"),
(-2.5, "-2.50000000"),
(0.123456785, "0.12345679"),
(0.1, "0.10000000"),
(1.0 / 3.0, "0.33333333"),
(100.0, "100.00000000"),
];
for (v, want) in cases {
assert_eq!(&cvt_double_to_string(*v, 8), want, "cvtDoubleToString({v})");
}
}
#[test]
fn to_string_spells_nan_by_hand() {
assert_eq!(to_string(f64::NAN), "NaN");
assert_eq!(to_string(-f64::NAN), "NaN");
}
#[test]
fn exponent_has_a_sign_and_two_digits() {
assert_eq!(fmt_e(1.5, 2, false), "1.50e+00");
assert_eq!(fmt_e(1.5e-7, 3, false), "1.500e-07");
assert_eq!(fmt_e(1.5e120, 1, true), "1.5E+120");
assert_eq!(fmt_e(0.0, 6, false), "0.000000e+00");
}
#[test]
fn g_switches_form_and_drops_trailing_zeros() {
assert_eq!(fmt_g(100000.0, 6, false, false), "100000");
assert_eq!(fmt_g(1000000.0, 6, false, false), "1e+06");
assert_eq!(fmt_g(0.0001, 6, false, false), "0.0001");
assert_eq!(fmt_g(0.00001, 6, false, false), "1e-05");
assert_eq!(fmt_g(1e-7, 6, true, false), "1E-07");
assert_eq!(fmt_g(3.14159265, 6, false, false), "3.14159");
assert_eq!(fmt_g(1.5, 6, false, false), "1.5");
assert_eq!(fmt_g(0.0, 6, false, false), "0");
assert_eq!(fmt_g(1.5, 6, false, true), "1.50000");
}
#[test]
fn a_precision_above_eight_is_always_exponential_and_width_padded() {
assert_eq!(cvt_double_to_string(1.0, 10), " 1.0000000000e+00");
assert_eq!(cvt_double_to_string(1.0, 9), " 1.000000000e+00");
assert_eq!(
cvt_double_to_string(1.0, 30).trim_start(),
"1.00000000000000000e+00"
);
}
}