@@ -119,14 +119,25 @@ strings round-trip readably.")
119119 (doc str "renders an `Form` back to source-like text. Round-trips
120120 for syntactic content; whitespace around comments is left to the
121121 caller's formatting pass.")
122+ (private double-round-trips?)
123+ (hidden double-round-trips?)
124+ (defn double-round-trips? [s d]
125+ (match (Double.from-string s)
126+ (Maybe.Just d2) (Double.= d2 d)
127+ (Maybe.Nothing) false))
128+
122129 (private double-str)
123130 (hidden double-str)
124- (doc double-str "renders a `Double` keeping it lexically a double.
125- `Double.str 10.0` returns `\"10\"`, which would round-trip as an `Int`;
126- scientific notation (`1e+09`) round-trips fine on its own. Append `.0`
127- only when the output has neither a decimal point nor an exponent.")
131+ (doc double-str "renders a `Double` so it round-trips and stays
132+ lexically a double. `Double.str` uses `%g` (6 sig figs by default),
133+ which loses precision for values like `3.14159265358979`. We try that
134+ first; if it doesn't parse back to the same `Double`, fall back to
135+ `%.17g` (17 sig figs guarantees exact round-trip for IEEE 754).
136+ Finally, append `.0` when the result has no decimal point or exponent,
137+ so the source reads as a double instead of an int.")
128138 (defn double-str [d]
129139 (let-do [s (Double.str d)]
140+ (unless (double-round-trips? &s d) (set! s (Double.format "%.17g" d)))
130141 (if (or
131142 (String.contains? &s \.)
132143 (or (String.contains? &s \e) (String.contains? &s \E)))
0 commit comments