1use std::fmt::Write as _;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DotZero {
14 Add,
16 Omit,
18}
19
20#[must_use]
22pub fn float_repr(value: f64, dot_zero: DotZero) -> String {
23 if value.is_nan() {
24 return "nan".to_owned();
25 }
26 if value.is_infinite() {
27 return if value < 0.0 { "-inf" } else { "inf" }.to_owned();
28 }
29
30 let sign = if value.is_sign_negative() { "-" } else { "" };
31 let (digits, exponent) = shortest_digits(value.abs());
32
33 let decpt = exponent + 1;
36 let mut out = String::with_capacity(digits.len() + 8);
37 out.push_str(sign);
38
39 if decpt <= -4 || decpt > 16 {
40 out.push_str(&digits[..1]);
41 if digits.len() > 1 {
42 out.push('.');
43 out.push_str(&digits[1..]);
44 }
45 let exp = decpt - 1;
46 let _ = write!(out, "e{}{:02}", if exp < 0 { '-' } else { '+' }, exp.abs());
47 } else if decpt <= 0 {
48 out.push_str("0.");
49 for _ in 0..-decpt {
50 out.push('0');
51 }
52 out.push_str(&digits);
53 } else {
54 let at = usize::try_from(decpt).expect("decpt is positive and small here");
57 if at >= digits.len() {
58 out.push_str(&digits);
59 for _ in 0..(at - digits.len()) {
60 out.push('0');
61 }
62 if dot_zero == DotZero::Add {
63 out.push_str(".0");
64 }
65 } else {
66 out.push_str(&digits[..at]);
67 out.push('.');
68 out.push_str(&digits[at..]);
69 }
70 }
71 out
72}
73
74fn shortest_digits(value: f64) -> (String, i32) {
80 let formatted = format!("{value:e}");
81 let (mantissa, exponent) = formatted
82 .split_once('e')
83 .expect("Rust always writes an exponent in `{:e}` form");
84 let digits = mantissa.replace('.', "");
85 let exponent = exponent
86 .parse::<i32>()
87 .expect("Rust always writes a decimal exponent");
88 (digits, exponent)
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn repr(value: f64) -> String {
96 float_repr(value, DotZero::Add)
97 }
98
99 #[test]
100 fn a_float_keeps_a_trailing_zero_and_an_imaginary_does_not() {
101 assert_eq!(repr(100.0), "100.0");
102 assert_eq!(float_repr(100.0, DotZero::Omit), "100");
103 assert_eq!(float_repr(0.0, DotZero::Omit), "0");
104 }
105
106 #[test]
107 fn the_switch_to_exponential_notation_is_where_cpython_puts_it() {
108 assert_eq!(repr(1e15), "1000000000000000.0");
109 assert_eq!(repr(1e16), "1e+16");
110 assert_eq!(repr(0.0001), "0.0001");
111 assert_eq!(repr(0.00001), "1e-05");
112 }
113
114 #[test]
115 fn an_overflowing_literal_is_infinity_and_prints_as_one() {
116 assert_eq!(repr(f64::INFINITY), "inf");
117 assert_eq!(repr(f64::NEG_INFINITY), "-inf");
118 assert_eq!(repr(f64::NAN), "nan");
119 }
120}