Skip to content

Commit 1030595

Browse files
committed
double-str: round-trip check, fall back to %.17g for precision-losing values
1 parent c7a2930 commit 1030595

2 files changed

Lines changed: 25 additions & 8 deletions

File tree

carp-reader.carp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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)))

test/carp-reader.carp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,18 @@
156156
"10.0"
157157
&(Located.str &(first-form "10.0"))
158158
"10.0 round-trips as Double, not Int")
159-
; Big doubles render in scientific notation; no `.0` should be
160-
; appended in that case (it would produce invalid `1e+09.0`).
159+
; Whole-number doubles that don't round-trip through `%g` fall back
160+
; to `%.17g` (which renders an integer without exponent) and then get
161+
; `.0` appended so they still parse as Double.
161162
(assert-equal t
162-
"1e+09"
163+
"999999999.0"
163164
&(Located.str &(first-form "999999999.0"))
164-
"large Double keeps scientific notation, no spurious .0")
165+
"999999999.0 round-trips with .0 suffix, not lossy scientific notation")
166+
; A Double written with high precision keeps that precision.
167+
(assert-equal t
168+
"3.14159265358979"
169+
&(Located.str &(first-form "3.14159265358979"))
170+
"high-precision Double round-trips bit-exact")
165171

166172
; --- end positions track the byte after the form ---
167173
(assert-equal t

0 commit comments

Comments
 (0)