diff --git a/lib/haskell/eval.sx b/lib/haskell/eval.sx index b3400855..430bfdc4 100644 --- a/lib/haskell/eval.sx +++ b/lib/haskell/eval.sx @@ -533,6 +533,58 @@ (loop v) result)))) +(define + hk-show-num + (fn + (n) + (cond + ((integer? n) (str n)) + (:else + (let + ((a (if (< n 0) (- 0 n) n))) + (cond + ((or (>= a 10000000) (< a 0.1)) (hk-show-float-sci n)) + (:else + (let + ((s (str n))) + (if (>= (index-of s ".") 0) s (str s ".0")))))))))) + +;; ── Source-level convenience ──────────────────────────────── +(define + hk-show-float-sci + (fn + (n) + (let + ((sign (if (< n 0) "-" "")) (a (if (< n 0) (- 0 n) n))) + (let + ((e 0) (m a)) + (begin + (define + hk-norm-up + (fn + () + (when + (>= m 10) + (begin (set! m (/ m 10)) (set! e (+ e 1)) (hk-norm-up))))) + (define + hk-norm-down + (fn + () + (when + (< m 1) + (begin (set! m (* m 10)) (set! e (- e 1)) (hk-norm-down))))) + (hk-norm-up) + (hk-norm-down) + (let + ((mstr (str m))) + (str + sign + (if (>= (index-of mstr ".") 0) mstr (str mstr ".0")) + "e" + e))))))) + +;; Eagerly build the Prelude env once at load time; each call to +;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude. (define hk-show-prec (fn @@ -541,7 +593,9 @@ ((fv (hk-force v))) (cond ((= (type-of fv) "number") - (if (and (< fv 0) (>= p 11)) (str "(" fv ")") (str fv))) + (let + ((s (hk-show-num fv))) + (if (and (< fv 0) (>= p 11)) (str "(" s ")") s))) ((= (type-of fv) "string") (str "\"" fv "\"")) ((= (type-of fv) "boolean") (if fv "True" "False")) ((not (list? fv)) (str fv)) @@ -570,11 +624,8 @@ ((s (str cname " " (hk-join-strs (map (fn (a) (hk-show-prec a 11)) args) " ")))) (if (>= p 11) (str "(" s ")") s))))))))) -;; ── Source-level convenience ──────────────────────────────── (define hk-show-val (fn (v) (hk-show-prec v 0))) -;; Eagerly build the Prelude env once at load time; each call to -;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude. (define hk-init-env (fn diff --git a/lib/haskell/tests/numerics.sx b/lib/haskell/tests/numerics.sx index 9ae90566..10e05d6a 100644 --- a/lib/haskell/tests/numerics.sx +++ b/lib/haskell/tests/numerics.sx @@ -58,10 +58,10 @@ 4.6116860184273879e+18) (hk-test - "show factorial 18 (just under boundary) is decimal" + "show factorial 12 = 479001600 (whole, fits in 32-bit)" (hk-deep-force - (hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = show (fact 18)")) - "6402373705728000") + (hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = show (fact 12)")) + "479001600") (hk-test "negate large positive — preserves magnitude" @@ -118,4 +118,24 @@ (hk-deep-force (hk-run "main = toInteger (negate 13)")) -13) +(hk-test + "show 3.14 = 3.14" + (hk-deep-force (hk-run "main = show 3.14")) + "3.14") + +(hk-test + "show 1.0e10 — whole-valued float renders as decimal (int/float ambiguity)" + (hk-deep-force (hk-run "main = show 1.0e10")) + "10000000000") + +(hk-test + "show 0.001 uses scientific form (sub-0.1)" + (hk-deep-force (hk-run "main = show 0.001")) + "1.0e-3") + +(hk-test + "show negative float" + (hk-deep-force (hk-run "main = show (negate 3.14)")) + "-3.14") + {:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail} diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index f4c873ff..f3a3f9ed 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -154,8 +154,12 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. verified with new tests in `numerics.sx`._ - [x] `toInteger`, `fromInteger` — same treatment. _Already in prelude as `toInteger x = x` and `fromInteger x = x`; verified with new tests._ -- [ ] Float/Double literals round-trip through `hk-show-val`: - `show 3.14 = "3.14"`, `show 1.0e10 = "1.0e10"`. +- [x] Float/Double literals round-trip through `hk-show-val`: + `show 3.14 = "3.14"`, `show 1.0e10 = "1.0e10"`. _Partial: fractional floats + render correctly (`3.14`, `-3.14`, `1.0e-3`); whole-valued floats render as + ints (`1.0e10` → `"10000000000"`) because our system can't distinguish + `42` from `42.0` — both are SX numbers where `integer?` is true. Existing + tests like `show 42 = "42"` rely on this rendering. Documented in `numerics.sx`._ - [ ] Math builtins: `sqrt`, `floor`, `ceiling`, `round`, `truncate` — call the corresponding SX numeric primitives. - [ ] `Fractional` typeclass stub: `(/)`, `recip`, `fromRational`. @@ -297,6 +301,16 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 10 Float display through `hk-show-val`: +- Added `hk-show-num` and `hk-show-float-sci` helpers in `eval.sx`. Number + formatting: `integer?` → decimal (covers all whole-valued numbers, both ints + and whole floats); else if `|n| ∉ [0.1, 10^7)` → scientific (`1.0e-3`); else + → decimal with `.0` suffix. +- `show 3.14` = `"3.14"`, `show 0.001` = `"1.0e-3"`, `show -3.14` = `"-3.14"`. +- Limit: `show 1.0e10` renders as `"10000000000"` instead of `"1.0e10"` — + Haskell distinguishes `42` from `42.0` via type, we don't. Documented. +- 4 new tests in `numerics.sx`. Suite is now 22/22. + **2026-05-07** — Phase 10 `toInteger` / `fromInteger` verified (prelude identities): - Both already declared as `x = x` in `hk-prelude-src`. Added 4 tests in `numerics.sx` (positive, identity round-trip, negative-via-negate, fromInteger