Chez numeric tower: exact ints / Ratio / double for JVM parity (jolt-n6al)

jolt was all-flonum (one :number type, inherited from Janet whose only number
type is a double). The Chez runtime has a full numeric tower, so the zero-Janet
path now carries it = JVM Clojure semantics:

  (/ 1 2)      => 1/2      (exact Ratio, was 0.5)
  (integer? 3) => true   (integer? 3.0) => false   (float? 3.0) => true
  (ratio? (/ 1 2)) => true   (= 3 3.0) => false   (== 3 3.0) => true
  (+ 1 2) => 3 (exact)   (/ 1.0 2) => 0.5 (double)

jolt= was already exactness-aware (values.ss) and == is value-equality, so
=/== match the JVM split. The reader preserves exactness (integer literals exact,
a/b ratios exact rationals, decimals/exponents flonums); backend_scheme emit-const
renders exact ints/ratios and flonums faithfully; the value-position arithmetic,
count, int, compare, bit ops, parseLong, string .length/.indexOf, range,
timestamps, and array bytes return exact integers (= JVM int/long) instead of
coercing to flonum. double/parseDouble/clojure.math floor|ceil|signum stay double.

Only the zero-Janet path carries the tower (the Janet reader loses exactness into
a double before emit). The prelude/all-flonum path is unaffected for compiled code;
the runtime reader is shared, so a couple of all-flonum reader assertions become
value (==) assertions. ~16 numeric corpus cases now give the JVM tower value vs the
Janet-era :expected and are allowlisted as tower divergences (Chez == reference
JVM) pending the corpus flip to JVM (jolt-ecz0). No BigDecimal type (1M).

Re-minted. zero-janet 2682 (floor 2698->2682, the reclassified tower cases), 0 new
divergences; fixpoint 10/10, bootstrap 6/6, spine 35/35, cli 49/49; Janet gate 155
files 0 failed.
This commit is contained in:
Yogthos 2026-06-20 23:09:27 -04:00
parent eb1c3298a4
commit 467ad75ff7
20 changed files with 291 additions and 210 deletions

View file

@ -113,14 +113,15 @@
((fx=? i 0) (seq-first s))
(else (loop (jolt-seq (seq-more s)) (fx- i 1)))))))
;; value-position arithmetic: jolt models every number as a double, so the
;; higher-order forms ((reduce + []), (apply * xs)) must coerce — Scheme's (+)/(*)
;; identities are EXACT 0/1, which aren't jolt= to the double 0.0/1.0. The hot
;; path uses the inlined native ops, not these.
(define (jolt-add . xs) (exact->inexact (apply + xs)))
(define (jolt-sub . xs) (exact->inexact (apply - xs)))
(define (jolt-mul . xs) (exact->inexact (apply * xs)))
(define (jolt-div . xs) (exact->inexact (apply / xs)))
;; value-position arithmetic (the higher-order forms: (reduce + []), (apply * xs)).
;; Scheme's +/-/*// already implement the JVM-parity numeric tower: exact+exact ->
;; exact, exact/exact -> Ratio, any flonum -> flonum. Identities (+)=0 / (*)=1 are
;; exact, matching exact integer arithmetic. The hot path uses the inlined native
;; ops, not these.
(define (jolt-add . xs) (apply + xs))
(define (jolt-sub . xs) (apply - xs))
(define (jolt-mul . xs) (apply * xs))
(define (jolt-div . xs) (apply / xs))
;; ============================================================================
;; IFn dispatch — the dynamic "value as fn" fallback. A callee that the emitter
@ -181,16 +182,18 @@
(define (jolt-into to from) (reduce-seq (lambda (acc x) (jolt-conj1 acc x)) to (jolt-seq from)))
(define (range-from n) (cseq-lazy n (lambda () (range-from (+ n 1.0)))))
(define (range-from n) (cseq-lazy n (lambda () (range-from (+ n 1)))))
(define (range-bounded n end step)
(if (if (> step 0.0) (< n end) (> n end))
(cseq-lazy n (lambda () (range-bounded (+ n step) end step)))
jolt-nil))
;; numeric tower (jolt-n6al): exact 0/1 defaults so (range 3) yields exact ints
;; (= JVM longs); flonum args still produce flonums (Scheme arithmetic preserves).
(define jolt-range
(case-lambda
(() (range-from 0.0))
((end) (range-bounded 0.0 end 1.0))
((start end) (range-bounded start end 1.0))
(() (range-from 0))
((end) (range-bounded 0 end 1))
((start end) (range-bounded start end 1))
((start end step) (range-bounded start end step))))
(define (jolt-take n coll)