Skip to main content

logicaffeine_data/
fmt.rs

1//! The ONE float-display path.
2//!
3//! Every engine that renders a LOGOS `Float` — the tree-walker, the bytecode
4//! VM, the AOT-compiled binary, and the direct-WASM host — must agree on the
5//! decimal string for the same `f64`, or the same program prints different
6//! answers depending on how it was run. This module is the single authority.
7//!
8//! The contract of [`fmt_f64`]:
9//!
10//! - **Shortest round-trip**: the fewest decimal digits that parse back to the
11//!   exact same bits (Rust's `{}` Display, Grisu/Dragon in std). A typed
12//!   literal echoes as typed; `1.0 / 3.0` shows all 17 significant digits.
13//! - **Never scientific notation**: `0.0000001` renders as `0.0000001`, not
14//!   `1e-7` — and a nonzero value NEVER renders as `0`.
15//! - **Integral floats stay bare**: `2.0` renders as `2`.
16//! - **Bounded width**: the longest possible output (the smallest subnormal,
17//!   `5e-324`) is under 340 bytes — the direct-WASM host's scratch size.
18
19/// Renders an `f64` exactly as LOGOS displays it on every engine.
20pub fn fmt_f64(f: f64) -> String {
21    format!("{f}")
22}
23
24#[cfg(test)]
25mod tests {
26    use super::fmt_f64;
27
28    #[test]
29    fn shortest_roundtrip_division() {
30        assert_eq!(fmt_f64(1.0 / 3.0), "0.3333333333333333");
31    }
32
33    #[test]
34    fn tiny_nonzero_is_never_zero_and_never_scientific() {
35        assert_eq!(fmt_f64(0.0000001), "0.0000001");
36    }
37
38    #[test]
39    fn typed_pi_echoes() {
40        assert_eq!(fmt_f64(3.141592653589793), "3.141592653589793");
41    }
42
43    #[test]
44    fn float_artifact_is_visible() {
45        assert_eq!(fmt_f64(0.1 + 0.2), "0.30000000000000004");
46    }
47
48    #[test]
49    fn integral_floats_stay_bare() {
50        assert_eq!(fmt_f64(2.0), "2");
51        assert_eq!(fmt_f64(100.0), "100");
52        assert_eq!(fmt_f64(-3.0), "-3");
53    }
54
55    #[test]
56    fn short_floats_unchanged() {
57        assert_eq!(fmt_f64(1.5), "1.5");
58        assert_eq!(fmt_f64(0.5), "0.5");
59        assert_eq!(fmt_f64(-1.5), "-1.5");
60    }
61
62    #[test]
63    fn non_finite_values() {
64        assert_eq!(fmt_f64(f64::NAN), "NaN");
65        assert_eq!(fmt_f64(f64::INFINITY), "inf");
66        assert_eq!(fmt_f64(f64::NEG_INFINITY), "-inf");
67    }
68
69    #[test]
70    fn negative_zero_keeps_its_sign() {
71        assert_eq!(fmt_f64(-0.0), "-0");
72        assert_eq!(fmt_f64(0.0), "0");
73    }
74
75    #[test]
76    fn huge_magnitudes_never_go_scientific() {
77        let s = fmt_f64(1e300);
78        assert!(!s.contains('e') && !s.contains('E'), "scientific leaked: {s}");
79        assert_eq!(s.len(), 301);
80        assert!(s.starts_with('1'));
81        let s = fmt_f64(f64::MAX);
82        assert!(!s.contains('e') && !s.contains('E'), "scientific leaked: {s}");
83    }
84
85    #[test]
86    fn subnormals_never_go_scientific() {
87        let s = fmt_f64(5e-324);
88        assert!(!s.contains('e') && !s.contains('E'), "scientific leaked: {s}");
89        assert!(s.starts_with("0.000"));
90    }
91
92    /// The direct-WASM host writes float display into a 340-byte scratch
93    /// buffer (`vm/wasm/module.rs`); the widest possible outputs must fit.
94    #[test]
95    fn worst_case_width_fits_wasm_scratch() {
96        for f in [5e-324, -5e-324, f64::MAX, f64::MIN, 1e300, -1e300] {
97            let s = fmt_f64(f);
98            assert!(s.len() <= 340, "{} bytes for {f:e}", s.len());
99        }
100    }
101
102    /// Shortest round-trip means parsing the output recovers the exact bits.
103    #[test]
104    fn output_roundtrips_bit_exactly() {
105        for f in [
106            1.0 / 3.0,
107            0.1 + 0.2,
108            3.141592653589793,
109            0.0000001,
110            -0.0,
111            1e300,
112            5e-324,
113            f64::MAX,
114            f64::MIN_POSITIVE,
115            123456789.123456789,
116        ] {
117            let parsed: f64 = fmt_f64(f).parse().unwrap();
118            assert_eq!(parsed.to_bits(), f.to_bits(), "round-trip drift for {f:e}");
119        }
120    }
121}