@@ -126,23 +126,48 @@ strings round-trip readably.")
126126 (Maybe.Just d2) (Double.= d2 d)
127127 (Maybe.Nothing) false))
128128
129+ (private decimal-only?)
130+ (hidden decimal-only?)
131+ (defn decimal-only? [s]
132+ (not (or (String.contains? s \e) (String.contains? s \E))))
133+
134+ (private strip-trailing-zeros)
135+ (hidden strip-trailing-zeros)
136+ (doc strip-trailing-zeros "for a decimal string containing `.`, drops
137+ trailing `0` digits but keeps at least one digit after the decimal point
138+ so the result remains lexically a `Double` (e.g. `\"10.000\"` becomes
139+ `\"10.0\"`, not `\"10.\"`).")
140+ (defn strip-trailing-zeros [s]
141+ (if (not (String.contains? s \.))
142+ @s
143+ (let-do [bs (String.chars s)
144+ n (Array.length &bs)
145+ end n]
146+ (while-do (and (Int.> end 2)
147+ (Char.= @(Array.unsafe-nth &bs (Int.dec end)) \0))
148+ (set! end (Int.dec end)))
149+ (when (Char.= @(Array.unsafe-nth &bs (Int.dec end)) \.)
150+ (set! end (Int.inc end)))
151+ (String.from-chars &(Array.prefix &bs end)))))
152+
129153 (private double-str)
130154 (hidden double-str)
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.")
155+ (doc double-str "renders a `Double` as a decimal literal that
156+ round-trips and parses with Carp's lexer (which does NOT accept
157+ scientific notation: only `digits.digits`).
158+
159+ Tries `Double.str` (`%g`, 6 sig figs) first, which is short and works
160+ for values the user wrote concisely. Falls back to `%.17g` (full
161+ round-trip precision). If either uses an exponent, expands to fixed
162+ decimal with `%.30f` and strips trailing zeros. Appends `.0` when the
163+ result has no decimal point so it parses as `Double`, not `Int`.")
138164 (defn double-str [d]
139165 (let-do [s (Double.str d)]
140- (unless (double-round-trips? &s d) (set! s (Double.format "%.17g" d)))
141- (if (or
142- (String.contains? &s \.)
143- (or (String.contains? &s \e) (String.contains? &s \E)))
144- s
145- (String.append &s ".0"))))
166+ (when (or (not (decimal-only? &s)) (not (double-round-trips? &s d)))
167+ (set! s (Double.format "%.17g" d)))
168+ (when (not (decimal-only? &s))
169+ (set! s (strip-trailing-zeros &(Double.format "%.30f" d))))
170+ (if (String.contains? &s \.) s (String.append &s ".0"))))
146171
147172 (defn str [a]
148173 (match-ref a
0 commit comments