use std::fmt::Write;
const I32_MIN: f64 = -2_147_483_648.0;
const I32_MAX: f64 = 2_147_483_647.0;
pub(crate) fn write_float(out: &mut String, v: f64) {
if v.is_nan() {
out.push_str(if v.is_sign_negative() { "-nan" } else { "nan" });
return;
}
if v.is_infinite() {
out.push_str(if v < 0.0 { "-inf" } else { "inf" });
return;
}
let t = v.trunc();
let t_in_range = (I32_MIN..=I32_MAX).contains(&t);
if v == t && t_in_range {
let _ = write!(out, "{v:.1}");
} else if v != t && t_in_range && (v - t).abs() < 1e-7 {
write_g15(out, v);
} else {
let _ = write!(out, "{v:.6}");
}
}
fn write_g15(out: &mut String, v: f64) {
let exp_form = format!("{v:.14e}");
let (mantissa, exponent) = exp_form
.split_once('e')
.expect("exponent formatting has an 'e'");
let x: i32 = exponent.parse().expect("the exponent is an integer");
if (-4..15).contains(&x) {
let precision = usize::try_from(14 - x).expect("x < 15");
let plain = format!("{v:.precision$}");
out.push_str(strip_zeros(&plain));
} else {
out.push_str(strip_zeros(mantissa));
out.push('e');
out.push(if x < 0 { '-' } else { '+' });
let _ = write!(out, "{:02}", x.unsigned_abs());
}
}
pub(crate) fn write_exact_float(out: &mut String, v: f64) -> Result<(), String> {
if v.is_nan() {
out.push_str("nan");
} else if v == f64::INFINITY {
out.push_str("inf");
} else if v == f64::NEG_INFINITY {
out.push_str("-1e308k");
} else {
write_finite(out, v, "float")?;
}
Ok(())
}
pub(crate) fn write_exact_time(out: &mut String, v: f64) -> Result<(), String> {
if v.is_nan() {
return Err("a NaN time, which no text reads back as (spec §5.4, §10.8)".to_owned());
} else if v == f64::INFINITY {
out.push_str("1e308ks");
} else if v == f64::NEG_INFINITY {
out.push_str("-1e308ks");
} else if v.is_subnormal() {
write_subnormal_time(out, v)?;
} else {
write_finite(out, v, "time")?;
out.push('s');
}
Ok(())
}
const SMALLEST_MS_TIME: f64 = f64::MIN_POSITIVE / 1000.0;
fn write_subnormal_time(out: &mut String, v: f64) -> Result<(), String> {
let gives_v = |m: f64| m.is_normal() && (m / 1000.0).to_bits() == v.to_bits();
let shortest = format!("{v:e}");
let (mantissa, exponent) = shortest
.split_once('e')
.expect("exponent formatting has an 'e'");
let exponent: i32 = exponent.parse().expect("the exponent is an integer");
let shifted = format!("{mantissa}e{}", exponent + 3);
if shifted.parse::<f64>().is_ok_and(gives_v) {
out.push_str(&shifted);
} else if let Some(m) = [v * 1000.0, f64::MIN_POSITIVE.copysign(v)]
.into_iter()
.find(|&m| gives_v(m))
{
let _ = write!(out, "{m:?}");
} else {
return Err(format!(
"the subnormal time {v:e}: a time below the normal range is written as a normal \
float followed by `ms`, and none gives a time closer to zero than \
{SMALLEST_MS_TIME:e} (spec §10.8)"
));
}
out.push_str("ms");
Ok(())
}
pub(crate) fn write_json_number(out: &mut String, v: f64, kind: &str) -> Result<(), String> {
if !v.is_finite() {
let what = if v.is_nan() { "NaN" } else { "infinite" };
let elsewhere = if v.is_nan() && kind == "time" {
"no text reads back as a NaN time in any format (spec §5.4)"
} else {
"the config and YAML formats write it"
};
return Err(format!(
"a {what} {kind}, which JSON has no number for (RFC 8259); {elsewhere}"
));
}
if v.is_subnormal() && kind == "time" {
return Err(format!(
"the subnormal time {v:e}: in JSON a time is its number of seconds, and no literal \
reads back as a subnormal number (spec §5.3); the config and YAML formats write it \
with `ms` (spec §10.8)"
));
}
write_finite(out, v, kind)
}
fn write_finite(out: &mut String, v: f64, kind: &str) -> Result<(), String> {
if v.is_subnormal() {
return Err(format!(
"the subnormal {kind} {v:e}: every literal below the normal range is an error \
(spec §5.3, §10.8)"
));
}
let start = out.len();
let _ = write!(out, "{v:?}");
debug_assert!(out[start..].contains(['.', 'e']), "{}", &out[start..]);
Ok(())
}
fn strip_zeros(s: &str) -> &str {
if !s.contains('.') {
return s;
}
let s = s.trim_end_matches('0');
s.strip_suffix('.').unwrap_or(s)
}
#[cfg(test)]
mod tests {
use super::*;
fn f(v: f64) -> String {
let mut s = String::new();
write_float(&mut s, v);
s
}
#[test]
fn rows_of_the_table() {
for (v, text) in [
(2.0, "2.0"),
(-0.0, "-0.0"),
(86400.0, "86400.0"),
(2147483647.0, "2147483647.0"),
(-2147483648.0, "-2147483648.0"),
(1e-10, "1e-10"),
(9e-08, "9e-08"),
(-9e-08, "-9e-08"),
(2.00000001, "2.00000001"),
(-2.00000001, "-2.00000001"),
(5.00000009, "5.00000009"),
(1.5, "1.500000"),
(0.1, "0.100000"),
(100.25, "100.250000"),
(3000000000.5, "3000000000.500000"),
(2147483648.0, "2147483648.000000"),
(-2147483649.0, "-2147483649.000000"),
(1e20, "100000000000000000000.000000"),
(f64::NAN, "nan"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "-inf"),
(2.99999999, "3.000000"),
(0.99999999, "1.000000"),
(-1.99999999, "-2.000000"),
(1e-7, "0.000000"),
(1.0000001, "1.000000"),
(0.0078125, "0.007812"),
(0.0234375, "0.023438"),
(0.001, "0.001000"),
] {
assert_eq!(f(v), text, "{v:?}");
}
assert_eq!(f(1e300).len(), 301 + 7);
}
#[test]
fn exact_forms() {
let float = |v: f64| {
let mut s = String::new();
write_exact_float(&mut s, v).map(|()| s)
};
let time = |v: f64| {
let mut s = String::new();
write_exact_time(&mut s, v).map(|()| s)
};
for (v, text) in [
(0.1, "0.1"),
(1.0, "1.0"),
(-0.0, "-0.0"),
(1e16, "1e16"),
(1.5e-10, "1.5e-10"),
(f64::MAX, "1.7976931348623157e308"),
(f64::MIN_POSITIVE, "2.2250738585072014e-308"),
(f64::NAN, "nan"),
(f64::INFINITY, "inf"),
(f64::NEG_INFINITY, "-1e308k"),
] {
assert_eq!(float(v).unwrap(), text, "{v:?}");
}
assert!(float(5e-324).is_err());
assert_eq!(time(1.5).unwrap(), "1.5s");
assert_eq!(time(0.001).unwrap(), "0.001s");
assert_eq!(time(-0.0).unwrap(), "-0.0s");
assert_eq!(time(f64::INFINITY).unwrap(), "1e308ks");
assert_eq!(time(f64::NEG_INFINITY).unwrap(), "-1e308ks");
assert!(time(f64::NAN).is_err());
assert_eq!(
format!("{SMALLEST_MS_TIME:.16e}"),
"2.2250738585069563e-311"
);
assert_eq!(time(-1e-310).unwrap(), "-1e-307ms");
assert_eq!(time(SMALLEST_MS_TIME).unwrap(), "2.2250738585072014e-308ms");
assert_eq!(
time(-SMALLEST_MS_TIME).unwrap(),
"-2.2250738585072014e-308ms"
);
let largest_subnormal = f64::MIN_POSITIVE - 5e-324;
assert!(largest_subnormal.is_subnormal());
assert!(time(largest_subnormal).unwrap().ends_with("e-305ms"));
let below = f64::from_bits(SMALLEST_MS_TIME.to_bits() - 1);
assert!(time(below).is_err());
assert!(time(-below).is_err());
assert!(time(5e-324).is_err());
let first = SMALLEST_MS_TIME.to_bits();
let end = f64::MIN_POSITIVE.to_bits();
let step = (end - first) / 20_011;
let spread = (0..20_011).map(|i| first + i * step);
for bits in (first..first + 100).chain(spread).chain(end - 100..end) {
for v in [f64::from_bits(bits), -f64::from_bits(bits)] {
let text = time(v).unwrap();
let m: f64 = text.strip_suffix("ms").unwrap().parse().unwrap();
assert!(
m.is_normal() && (m / 1000.0).to_bits() == v.to_bits(),
"{v:e}"
);
}
}
let json = |v: f64| {
let mut s = String::new();
write_json_number(&mut s, v, "time").map(|()| s)
};
assert_eq!(json(1.5).unwrap(), "1.5");
assert_eq!(json(-0.0).unwrap(), "-0.0");
assert_eq!(json(1e16).unwrap(), "1e16");
for v in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1e-310] {
assert!(json(v).is_err(), "{v:?}");
}
}
#[test]
fn fifteen_digit_forms() {
let g = |v: f64| {
let mut s = String::new();
write_g15(&mut s, v);
s
};
assert_eq!(g(2.2250738585072014e-308), "2.2250738585072e-308");
assert_eq!(g(5e-324), "4.94065645841247e-324");
assert_eq!(g(0.0001), "0.0001");
assert_eq!(g(0.00001), "1e-05");
assert_eq!(g(123456789012345.0), "123456789012345");
assert_eq!(g(1234567890123456.0), "1.23456789012346e+15");
assert_eq!(g(999.9999999999999), "1000");
}
}