Numbers-style category dispatch for binary numeric ops
Arithmetic and comparisons lowered to raw Chez ops, so an operand outside Chez's tower (BigDecimal) crashed with a raw condition, and Chez contagion leaked: (* 1.0 0) gave exact 0 where the JVM gives 0.0, (* ##Inf 0) gave 0 instead of ##NaN, (/ 1 0) raised an untyped error. One seam now (host/chez/seq.ss): call position emits jolt-n* macros with the both-Chez-numbers fast path open-coded; value position folds through the same binary ops. Anything outside the tower falls to per-op slow hooks that java/bigdec.ss extends, so bigdec arithmetic works in every position (the old static-only :bigdec typing limitation is gone). JVM rules patched into the fast path: a double operand wins, an exact zero divisor throws ArithmeticException while a double zero divisor yields Inf/NaN, quot/rem/mod cover ratios and doubles, min/max return the original operand with NaN winning, a nil operand is NPE and a non-number CCE, zero-arg -// throw ArityException at runtime instead of failing expansion. Also: with-precision now binds *math-context* and bigdec results round with real RoundingMode semantics (UNNECESSARY throws; division rounds to precision instead of throwing); rationalize goes through the shortest decimal print like BigDecimal.valueOf (the identity stub is gone); ratios coerce to bigdec like Numbers.toBigDecimal; min/max int-literal operands no longer coerce to flonum in the numeric pass. Perf neutral: fib and seq benches unchanged (the fast path is two type checks the optimizer folds); hinted fl/fx paths untouched. 19 JVM-certified corpus rows; cts baseline 5614->5730 pass, 192->88 errors, 84->79 baselined namespaces.
This commit is contained in:
parent
38abc1be84
commit
e66a91750e
13 changed files with 1106 additions and 729 deletions
|
|
@ -7,21 +7,19 @@
|
|||
;;
|
||||
;; Arithmetic follows java.math.BigDecimal's scale rules: add/sub align to the
|
||||
;; larger scale; multiply adds scales; divide gives the exact quotient at minimal
|
||||
;; scale or throws ArithmeticException on a non-terminating expansion. Clojure
|
||||
;; contagion: a bigdec mixed with an integer stays a bigdec; a flonum operand wins
|
||||
;; (the result is a double). jbd-add/-sub/-mul/-div, jbd-min/-max, the jbd-lt?/…
|
||||
;; /zero? helpers, and jbd-quot/-rem are the shared engine. Two paths reach it, both
|
||||
;; leaving the inlined native hot path untouched:
|
||||
;; - value position ((reduce + bigs)/(apply * bigs)): the jolt-add/-sub/-mul/-div
|
||||
;; and compare shims dispatch here when a bigdec operand is present.
|
||||
;; - call position ((+ 1.5M 2.5M), (< a b), (zero? b)): jolt.passes.numeric tags
|
||||
;; the invoke :num-kind :bigdec when every operand is statically a bigdec (M
|
||||
;; literal or a let-bound copy, integer literals allowed), and the back end
|
||||
;; lowers it to the jbd op. Non-bigdec code is unaffected.
|
||||
;; Gaps (a runtime bigdec the analyzer can't see statically): a bigdec mixed with a
|
||||
;; flonum in call position ((+ 1.5M 2.0)) and arithmetic over a bigdec the analyzer
|
||||
;; types as :any ((+ (bigdec x) 1)) fall through to the raw op and throw; use value
|
||||
;; position or a literal-typed let.
|
||||
;; scale or throws ArithmeticException on a non-terminating expansion (a bound
|
||||
;; *math-context* rounds instead). Clojure contagion: a bigdec mixed with an
|
||||
;; integer or ratio stays a bigdec; a flonum operand wins (the result is a
|
||||
;; double). jbd-add/-sub/-mul/-div, jbd-min/-max, the jbd-lt?/…/zero? helpers,
|
||||
;; and jbd-quot/-rem are the shared engine. Two paths reach it, both leaving the
|
||||
;; inlined fast path untouched:
|
||||
;; - the seq.ss binary dispatch: every generic op (any position — (+ (bigdec x)
|
||||
;; 1), (reduce + bigs), (quot 10.0 3M)) whose operand is outside Chez's tower
|
||||
;; falls to the jolt-*-slow hooks extended below.
|
||||
;; - static call position ((+ 1.5M 2.5M), (< a b), (zero? b)): jolt.passes.numeric
|
||||
;; tags the invoke :num-kind :bigdec when every operand is statically a bigdec
|
||||
;; (M literal or a let-bound copy, integer literals allowed), and the back end
|
||||
;; lowers it directly to the jbd op.
|
||||
|
||||
(define-record-type jbigdec (fields unscaled scale) (nongenerative chez-jbigdec-v1))
|
||||
|
||||
|
|
@ -79,11 +77,13 @@
|
|||
(define (jbigdec->flonum b)
|
||||
(exact->inexact (/ (jbigdec-unscaled b) (expt 10 (jbigdec-scale b)))))
|
||||
|
||||
;; coerce an exact integer to a scale-0 bigdec; pass a bigdec through. Used on the
|
||||
;; non-flonum mixed path (bigdec + long -> bigdec).
|
||||
;; coerce an exact operand to a bigdec; pass a bigdec through. Used on the
|
||||
;; non-flonum mixed path (bigdec + long -> bigdec). A Ratio converts like
|
||||
;; Numbers.toBigDecimal — exact decimal expansion or throw on non-terminating.
|
||||
(define (jbd-coerce x)
|
||||
(cond ((jbigdec? x) x)
|
||||
((and (number? x) (exact? x) (integer? x)) (make-jbigdec x 0))
|
||||
((and (number? x) (exact? x) (rational? x)) (jbd-rational->bigdec x))
|
||||
(else (error #f "bigdec arithmetic: cannot coerce operand" x))))
|
||||
|
||||
;; --- core arithmetic on the {unscaled, scale} pair --------------------------
|
||||
|
|
@ -117,12 +117,39 @@
|
|||
"java.lang.ArithmeticException"
|
||||
"Non-terminating decimal expansion; no exact representable decimal result.")))))))
|
||||
|
||||
;; floor(log10 |r|) for a nonzero exact rational.
|
||||
(define (jbd-exp10 r)
|
||||
(let ((n (abs (numerator r))) (d (denominator r)))
|
||||
(if (>= n d)
|
||||
(- (jbd-digits (quotient n d)) 1)
|
||||
(let loop ((x (* n 10)) (e -1))
|
||||
(if (>= x d) e (loop (* x 10) (- e 1)))))))
|
||||
;; round an exact rational to `prec` significant digits (the MathContext divide).
|
||||
(define (jbd-rational-prec r prec mode)
|
||||
(if (= r 0)
|
||||
(make-jbigdec 0 0)
|
||||
(let* ((neg (< r 0)) (ar (abs r))
|
||||
(s (- prec 1 (jbd-exp10 ar)))
|
||||
(scaled (* ar (expt 10 s)))
|
||||
(q (floor scaled)) (frac (- scaled q))
|
||||
(q2 (if (jbd-round-inc? q frac 1 mode neg) (+ q 1) q))
|
||||
(res (make-jbigdec (if neg (- q2) q2) s)))
|
||||
;; a carry can add a digit (9.99 -> 10.0); re-normalizing drops an exact
|
||||
;; trailing zero, never re-rounds.
|
||||
(if (> (jbd-digits q2) prec) (jbd-round-prec res prec mode) res))))
|
||||
|
||||
(define (jbd2-div a b)
|
||||
(when (= 0 (jbigdec-unscaled b))
|
||||
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Divide by zero")))
|
||||
;; a/b = (ua * 10^sb) / (ub * 10^sa) as an exact rational.
|
||||
(jbd-rational->bigdec (/ (* (jbigdec-unscaled a) (expt 10 (jbigdec-scale b)))
|
||||
(* (jbigdec-unscaled b) (expt 10 (jbigdec-scale a))))))
|
||||
;; a/b = (ua * 10^sb) / (ub * 10^sa) as an exact rational. Unlimited context:
|
||||
;; exact result at minimal scale or throw on a non-terminating expansion. A
|
||||
;; bound *math-context* instead rounds to its precision.
|
||||
(let ((r (/ (* (jbigdec-unscaled a) (expt 10 (jbigdec-scale b)))
|
||||
(* (jbigdec-unscaled b) (expt 10 (jbigdec-scale a)))))
|
||||
(mc (jbd-math-context)))
|
||||
(if mc
|
||||
(jbd-rational-prec r (jbd-mc-precision mc) (jbd-mc-mode mc))
|
||||
(jbd-rational->bigdec r))))
|
||||
|
||||
;; integer-division semantics (quot/rem): truncate toward zero, scale 0.
|
||||
(define (jbd-int-quot a b)
|
||||
|
|
@ -139,13 +166,65 @@
|
|||
(define (jbd-compare2 a b)
|
||||
(let-values (((ua ub s) (jbd-align a b))) (cond ((< ua ub) -1) ((> ua ub) 1) (else 0))))
|
||||
|
||||
;; --- *math-context* (with-precision) -----------------------------------------
|
||||
;; with-precision binds clojure.core/*math-context* to {:precision N :rounding
|
||||
;; MODE}; every exact bigdec result rounds through it (java.math.MathContext).
|
||||
(define jbd-kw-precision (keyword #f "precision"))
|
||||
(define jbd-kw-rounding (keyword #f "rounding"))
|
||||
(define (jbd-math-context)
|
||||
(let ((mc (var-deref "clojure.core" "*math-context*")))
|
||||
(if (jolt-nil? mc) #f mc)))
|
||||
(define (jbd-mc-precision mc) (jolt-get mc jbd-kw-precision))
|
||||
(define (jbd-mc-mode mc)
|
||||
(let ((r (jolt-get mc jbd-kw-rounding)))
|
||||
(cond ((symbol-t? r) (symbol-t-name r))
|
||||
((string? r) r)
|
||||
(else "HALF_UP"))))
|
||||
|
||||
;; should |value| = q + r/div (0 <= r < div) round up in magnitude? neg is the
|
||||
;; value's sign; r/div may be exact rationals (the division path).
|
||||
(define (jbd-round-inc? q r div mode neg)
|
||||
(cond ((= r 0) #f)
|
||||
((string=? mode "UP") #t)
|
||||
((string=? mode "DOWN") #f)
|
||||
((string=? mode "CEILING") (not neg))
|
||||
((string=? mode "FLOOR") neg)
|
||||
((string=? mode "HALF_DOWN") (> (* 2 r) div))
|
||||
((string=? mode "HALF_EVEN")
|
||||
(let ((c (- (* 2 r) div)))
|
||||
(cond ((> c 0) #t) ((< c 0) #f) (else (odd? q)))))
|
||||
((string=? mode "UNNECESSARY")
|
||||
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Rounding necessary")))
|
||||
(else (>= (* 2 r) div)))) ; HALF_UP, the MathContext default
|
||||
|
||||
(define (jbd-digits n) (string-length (number->string (abs n))))
|
||||
;; round a bigdec to `prec` significant digits with `mode` (a RoundingMode name).
|
||||
(define (jbd-round-prec bd prec mode)
|
||||
(let ((u (jbigdec-unscaled bd)) (s (jbigdec-scale bd)))
|
||||
(if (= u 0)
|
||||
bd
|
||||
(let ((digs (jbd-digits u)))
|
||||
(if (<= digs prec)
|
||||
bd
|
||||
(let* ((drop (- digs prec)) (div (expt 10 drop))
|
||||
(neg (< u 0)) (au (abs u))
|
||||
(q (quotient au div)) (r (remainder au div))
|
||||
(q2 (if (jbd-round-inc? q r div mode neg) (+ q 1) q))
|
||||
(res (make-jbigdec (if neg (- q2) q2) (- s drop))))
|
||||
;; a carry can add a digit back (99 -> 100 at precision 2)
|
||||
(if (> (jbd-digits q2) prec) (jbd-round-prec res prec mode) res)))))))
|
||||
(define (jbd-mc-round x)
|
||||
(let ((mc (and (jbigdec? x) (jbd-math-context))))
|
||||
(if mc (jbd-round-prec x (jbd-mc-precision mc) (jbd-mc-mode mc)) x)))
|
||||
|
||||
;; A binary op over operands that may mix bigdec / integer / flonum. flonum-op is
|
||||
;; the native fallback for the double-contagion path; bd-op is the exact bigdec op.
|
||||
;; the native fallback for the double-contagion path; bd-op is the exact bigdec op
|
||||
;; (its result rounds through a bound *math-context*).
|
||||
(define (jbd-binop flonum-op bd-op a b)
|
||||
(if (or (flonum? a) (flonum? b))
|
||||
(flonum-op (if (jbigdec? a) (jbigdec->flonum a) a)
|
||||
(if (jbigdec? b) (jbigdec->flonum b) b))
|
||||
(bd-op (jbd-coerce a) (jbd-coerce b))))
|
||||
(jbd-mc-round (bd-op (jbd-coerce a) (jbd-coerce b)))))
|
||||
|
||||
;; --- variadic engine ops (Phase-2 emit targets + value-position folds) -------
|
||||
(define (jbd-fold flonum-op bd-op init xs)
|
||||
|
|
@ -203,23 +282,78 @@
|
|||
;; --- wire into the value model ----------------------------------------------
|
||||
(def-var! "clojure.core" "bigdec" jolt-bigdec)
|
||||
|
||||
;; Value-position arithmetic: (reduce + bigs) / (apply * bigs) pass +/*/- // AS A
|
||||
;; VALUE, which lowers to these shims (NOT the inlined hot-path native op). Extend
|
||||
;; them to dispatch to the bigdec engine when a bigdec operand is present; ordinary
|
||||
;; numeric folds hit the captured native path unchanged.
|
||||
(define jbd-prev-add jolt-add)
|
||||
(define jbd-prev-sub jolt-sub)
|
||||
(define jbd-prev-mul jolt-mul)
|
||||
(define jbd-prev-div jolt-div)
|
||||
(define jbd-prev-min jolt-min)
|
||||
(define jbd-prev-max jolt-max)
|
||||
(define (jbd-any? xs) (and (pair? xs) (or (jbigdec? (car xs)) (jbd-any? (cdr xs)))))
|
||||
(set! jolt-add (lambda xs (if (jbd-any? xs) (apply jbd-add xs) (apply jbd-prev-add xs))))
|
||||
(set! jolt-sub (lambda xs (if (jbd-any? xs) (apply jbd-sub xs) (apply jbd-prev-sub xs))))
|
||||
(set! jolt-mul (lambda xs (if (jbd-any? xs) (apply jbd-mul xs) (apply jbd-prev-mul xs))))
|
||||
(set! jolt-div (lambda xs (if (jbd-any? xs) (apply jbd-div xs) (apply jbd-prev-div xs))))
|
||||
(set! jolt-min (lambda xs (if (jbd-any? xs) (apply jbd-min xs) (apply jbd-prev-min xs))))
|
||||
(set! jolt-max (lambda xs (if (jbd-any? xs) (apply jbd-max xs) (apply jbd-prev-max xs))))
|
||||
;; The seq.ss binary numeric dispatch (jolt-add2/… and the jolt-n* macros) routes
|
||||
;; any op whose operand is outside Chez's tower to the *-slow hooks; extend each
|
||||
;; with a bigdec arm. Every arithmetic position (call, value, higher-order)
|
||||
;; funnels through these, so contagion and *math-context* rounding apply
|
||||
;; uniformly. min/max need no arm: the generic jolt-min2 compares through
|
||||
;; jolt-num-cmp-slow and returns the original operand.
|
||||
(set! jolt-num-slow?
|
||||
(let ((prev jolt-num-slow?)) (lambda (x) (or (jbigdec? x) (prev x)))))
|
||||
(define (jbd-extend-hook prev bd-op)
|
||||
(lambda (a b)
|
||||
(if (or (jbigdec? a) (jbigdec? b)) (bd-op a b) (prev a b))))
|
||||
(set! jolt-add-slow (jbd-extend-hook jolt-add-slow (lambda (a b) (jbd-binop + jbd2+ a b))))
|
||||
(set! jolt-sub-slow (jbd-extend-hook jolt-sub-slow (lambda (a b) (jbd-binop - jbd2- a b))))
|
||||
(set! jolt-mul-slow (jbd-extend-hook jolt-mul-slow (lambda (a b) (jbd-binop * jbd2* a b))))
|
||||
(set! jolt-div-slow (jbd-extend-hook jolt-div-slow (lambda (a b) (jbd-binop / jbd2-div a b))))
|
||||
(set! jolt-num-cmp-slow
|
||||
(let ((prev jolt-num-cmp-slow))
|
||||
(lambda (a b)
|
||||
(if (and (or (jbigdec? a) (jbigdec? b)) (jbd-numberish? a) (jbd-numberish? b))
|
||||
(jbd-value-compare a b)
|
||||
(prev a b)))))
|
||||
;; quot/rem/mod: a double operand demotes to the double path; exact operands use
|
||||
;; the integer-division bigdec ops (mod = rem, floor-adjusted to the divisor's sign).
|
||||
(define (jbd->num x) (if (jbigdec? x) (jbigdec->flonum x) x))
|
||||
(set! jolt-quot-slow
|
||||
(jbd-extend-hook jolt-quot-slow
|
||||
(lambda (a b) (if (or (flonum? a) (flonum? b))
|
||||
(jolt-quot (jbd->num a) (jbd->num b))
|
||||
(jbd-int-quot (jbd-coerce a) (jbd-coerce b))))))
|
||||
(set! jolt-rem-slow
|
||||
(jbd-extend-hook jolt-rem-slow
|
||||
(lambda (a b) (if (or (flonum? a) (flonum? b))
|
||||
(jolt-rem (jbd->num a) (jbd->num b))
|
||||
(jbd-int-rem (jbd-coerce a) (jbd-coerce b))))))
|
||||
(set! jolt-mod-slow
|
||||
(jbd-extend-hook jolt-mod-slow
|
||||
(lambda (a b)
|
||||
(if (or (flonum? a) (flonum? b))
|
||||
(jolt-mod (jbd->num a) (jbd->num b))
|
||||
(let* ((bb (jbd-coerce b))
|
||||
(m (jbd-int-rem (jbd-coerce a) bb)))
|
||||
(if (or (jbd-zero? m) (eq? (jbd-neg? m) (jbd-neg? bb))) m (jbd2+ m bb)))))))
|
||||
;; unary shims: inc/dec and the sign predicates take a bigdec arm. set! updates
|
||||
;; call-position references; the re-def-var! updates the var cell AND claims the
|
||||
;; wrapped proc's class name before the prelude's inc'/dec' aliases are defined
|
||||
;; ((type inc) stays clojure.core$inc — first def wins in the class registry).
|
||||
(define jbd-one (make-jbigdec 1 0))
|
||||
(set! jolt-inc (let ((prev jolt-inc)) (lambda (x) (if (jbigdec? x) (jbd-mc-round (jbd2+ x jbd-one)) (prev x)))))
|
||||
(set! jolt-dec (let ((prev jolt-dec)) (lambda (x) (if (jbigdec? x) (jbd-mc-round (jbd2- x jbd-one)) (prev x)))))
|
||||
(set! jolt-zero? (let ((prev jolt-zero?)) (lambda (x) (if (jbigdec? x) (jbd-zero? x) (prev x)))))
|
||||
(set! jolt-pos? (let ((prev jolt-pos?)) (lambda (x) (if (jbigdec? x) (jbd-pos? x) (prev x)))))
|
||||
(set! jolt-neg? (let ((prev jolt-neg?)) (lambda (x) (if (jbigdec? x) (jbd-neg? x) (prev x)))))
|
||||
(def-var! "clojure.core" "inc" jolt-inc)
|
||||
(def-var! "clojure.core" "dec" jolt-dec)
|
||||
(def-var! "clojure.core" "zero?" jolt-zero?)
|
||||
(def-var! "clojure.core" "pos?" jolt-pos?)
|
||||
(def-var! "clojure.core" "neg?" jolt-neg?)
|
||||
|
||||
;; rationalize: reference Clojure goes through BigDecimal.valueOf(double) — the
|
||||
;; SHORTEST decimal print of the double, not its exact binary value — so
|
||||
;; (rationalize 1.1) is 11/10. A bigdec is exact already; other exacts pass through.
|
||||
(define (jolt-rationalize x)
|
||||
(cond ((jbigdec? x) (/ (jbigdec-unscaled x) (expt 10 (jbigdec-scale x))))
|
||||
((flonum? x)
|
||||
(if (or (nan? x) (infinite? x))
|
||||
(jolt-throw (jolt-host-throwable "java.lang.NumberFormatException"
|
||||
(string-append "Invalid input: " (number->string x))))
|
||||
(let ((bd (jolt-bigdec-from-string (jolt-num->string x))))
|
||||
(/ (jbigdec-unscaled bd) (expt 10 (jbigdec-scale bd))))))
|
||||
((number? x) x)
|
||||
(else (jolt-num-cast-throw x))))
|
||||
(def-var! "clojure.core" "rationalize" jolt-rationalize)
|
||||
|
||||
;; compare: add a bigdec arm (enables compare / sort / sorted collections). A
|
||||
;; bigdec vs a plain number compares by value; bigdec vs bigdec is scale-independent.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
212
host/chez/seq.ss
212
host/chez/seq.ss
|
|
@ -158,17 +158,209 @@
|
|||
((fx=? i 0) (seq-first s))
|
||||
(else (loop (jolt-seq (seq-more s)) (fx- i 1)))))))
|
||||
|
||||
;; --- checked arithmetic: JVM Numbers.ops-style category dispatch -------------
|
||||
;; Every arithmetic/comparison site (the inlined jolt-n* macros in call position,
|
||||
;; the variadic shims in value position) funnels a binary op through ONE dispatch:
|
||||
;; both operands inside Chez's tower take the native op with JVM contagion rules
|
||||
;; patched in (a double operand wins — Chez's exact-zero shortcut must not leak:
|
||||
;; (* 1.5 0) is 0.0, not 0; an exact zero divisor throws ArithmeticException, a
|
||||
;; double zero divisor yields ##Inf/##NaN); an operand OUTSIDE the tower (e.g.
|
||||
;; BigDecimal) falls to a slow hook the numeric shim extends (java/bigdec.ss).
|
||||
;; A non-numeric operand is a ClassCastException, like the JVM.
|
||||
(define (jolt-num-cast-throw x)
|
||||
(if (jolt-nil? x)
|
||||
(jolt-throw (jolt-host-throwable "java.lang.NullPointerException" ""))
|
||||
(jolt-throw (jolt-host-throwable
|
||||
"java.lang.ClassCastException"
|
||||
(string-append "class " (jolt-class-name x)
|
||||
" cannot be cast to class java.lang.Number")))))
|
||||
(define (jolt-div0-throw)
|
||||
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Divide by zero")))
|
||||
|
||||
;; slow hooks: one per op, taking over when an operand is outside Chez's tower.
|
||||
;; A numeric shim (java/bigdec.ss) set!-extends them; the base case is the JVM's:
|
||||
;; not a number -> ClassCastException. The hooks are BINARY and never re-enter
|
||||
;; the variadic shims, so extension order can't recurse.
|
||||
(define (jolt-add-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-sub-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-mul-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-div-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
;; comparison of operands outside the Chez tower: numeric shims extend this to a
|
||||
;; 3-way compare; anything left over is not a number.
|
||||
(define (jolt-num-cmp-slow a b)
|
||||
(jolt-num-cast-throw (if (number? a) b a)))
|
||||
|
||||
(define (jolt-add2 a b)
|
||||
(if (and (number? a) (number? b)) (+ a b) (jolt-add-slow a b)))
|
||||
(define (jolt-sub2 a b)
|
||||
(if (and (number? a) (number? b)) (- a b) (jolt-sub-slow a b)))
|
||||
(define (jolt-mul2 a b)
|
||||
(if (and (number? a) (number? b))
|
||||
(if (or (flonum? a) (flonum? b))
|
||||
(fl* (real->flonum a) (real->flonum b))
|
||||
(* a b))
|
||||
(jolt-mul-slow a b)))
|
||||
(define (jolt-div2 a b)
|
||||
(if (and (number? a) (number? b))
|
||||
(if (or (flonum? a) (flonum? b))
|
||||
(fl/ (real->flonum a) (real->flonum b))
|
||||
(if (eqv? b 0) (jolt-div0-throw) (/ a b)))
|
||||
(jolt-div-slow a b)))
|
||||
(define (jolt-lt2 a b)
|
||||
(if (and (number? a) (number? b)) (< a b) (< (jolt-num-cmp-slow a b) 0)))
|
||||
(define (jolt-gt2 a b)
|
||||
(if (and (number? a) (number? b)) (> a b) (> (jolt-num-cmp-slow a b) 0)))
|
||||
(define (jolt-le2 a b)
|
||||
(if (and (number? a) (number? b)) (<= a b) (<= (jolt-num-cmp-slow a b) 0)))
|
||||
(define (jolt-ge2 a b)
|
||||
(if (and (number? a) (number? b)) (>= a b) (>= (jolt-num-cmp-slow a b) 0)))
|
||||
;; min/max return the ORIGINAL operand (type and exactness kept, like
|
||||
;; Numbers.min): (min 1 2.0) is 1, not 1.0. A NaN operand wins.
|
||||
(define (jolt-min2 a b)
|
||||
(cond ((and (flonum? a) (nan? a)) a)
|
||||
((and (flonum? b) (nan? b)) b)
|
||||
(else (if (jolt-lt2 a b) a b))))
|
||||
(define (jolt-max2 a b)
|
||||
(cond ((and (flonum? a) (nan? a)) a)
|
||||
((and (flonum? b) (nan? b)) b)
|
||||
(else (if (jolt-gt2 a b) a b))))
|
||||
|
||||
;; quot/rem/mod over the full tower: truncating division; a double operand makes
|
||||
;; the result a double; mod has floor semantics (result takes the divisor's
|
||||
;; sign). A zero divisor throws ArithmeticException in both worlds (JVM double
|
||||
;; quot/rem check the divisor before dividing). Non-tower operands hit the
|
||||
;; set!-extensible slow hooks.
|
||||
(define (jolt-quot-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-rem-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-mod-slow a b) (jolt-num-cast-throw (if (number? a) b a)))
|
||||
(define (jolt-quot a b)
|
||||
(cond ((not (and (number? a) (number? b))) (jolt-quot-slow a b))
|
||||
((or (flonum? a) (flonum? b))
|
||||
(let ((n (real->flonum a)) (d (real->flonum b)))
|
||||
(if (fl= d 0.0) (jolt-div0-throw) (fltruncate (fl/ n d)))))
|
||||
((eqv? b 0) (jolt-div0-throw))
|
||||
((and (integer? a) (integer? b)) (quotient a b))
|
||||
(else (truncate (/ a b)))))
|
||||
(define (jolt-rem a b)
|
||||
(cond ((not (and (number? a) (number? b))) (jolt-rem-slow a b))
|
||||
((or (flonum? a) (flonum? b))
|
||||
(let ((n (real->flonum a)) (d (real->flonum b)))
|
||||
(if (fl= d 0.0) (jolt-div0-throw)
|
||||
(fl- n (fl* d (fltruncate (fl/ n d)))))))
|
||||
((eqv? b 0) (jolt-div0-throw))
|
||||
((and (integer? a) (integer? b)) (remainder a b))
|
||||
(else (- a (* b (truncate (/ a b)))))))
|
||||
(define (jolt-mod a b)
|
||||
(cond ((not (and (number? a) (number? b))) (jolt-mod-slow a b))
|
||||
((and (integer? a) (integer? b) (not (flonum? a)) (not (flonum? b)))
|
||||
(if (eqv? b 0) (jolt-div0-throw) (modulo a b)))
|
||||
(else
|
||||
(let ((m (jolt-rem a b)))
|
||||
(if (or (zero? m) (eq? (negative? m) (negative? b))) m (jolt-add2 m b))))))
|
||||
|
||||
;; 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))
|
||||
(define (jolt-min . xs) (apply min xs))
|
||||
(define (jolt-max . xs) (apply max xs))
|
||||
;; Folded through the binary dispatch so contagion/edge rules hold; identities
|
||||
;; (+)=0 / (*)=1 are exact, matching exact integer arithmetic. The hot path uses
|
||||
;; the inlined native ops, not these.
|
||||
;; recognizer for slow-path numeric types; numeric shims extend it.
|
||||
(define (jolt-num-slow? x) #f)
|
||||
(define (jolt-num-check1 x) ; (+ x)/(* x) return x but still type-check it
|
||||
(if (or (number? x) (jolt-num-slow? x)) x (jolt-num-cast-throw x)))
|
||||
(define (jolt-add . xs)
|
||||
(cond ((null? xs) 0)
|
||||
((null? (cdr xs)) (jolt-num-check1 (car xs)))
|
||||
(else (fold-left jolt-add2 (car xs) (cdr xs)))))
|
||||
(define (jolt-arity0-throw name)
|
||||
(jolt-throw (jolt-host-throwable
|
||||
"clojure.lang.ArityException"
|
||||
(string-append "Wrong number of args (0) passed to: clojure.core/" name))))
|
||||
(define (jolt-sub . xs)
|
||||
(cond ((null? xs) (jolt-arity0-throw "-"))
|
||||
((null? (cdr xs)) (jolt-sub2 0 (car xs)))
|
||||
(else (fold-left jolt-sub2 (car xs) (cdr xs)))))
|
||||
(define (jolt-mul . xs)
|
||||
(cond ((null? xs) 1)
|
||||
((null? (cdr xs)) (jolt-num-check1 (car xs)))
|
||||
(else (fold-left jolt-mul2 (car xs) (cdr xs)))))
|
||||
(define (jolt-div . xs)
|
||||
(cond ((null? xs) (jolt-arity0-throw "/"))
|
||||
((null? (cdr xs)) (jolt-div2 1 (car xs)))
|
||||
(else (fold-left jolt-div2 (car xs) (cdr xs)))))
|
||||
(define (jolt-min x . xs) (fold-left jolt-min2 x xs))
|
||||
(define (jolt-max x . xs) (fold-left jolt-max2 x xs))
|
||||
;; variadic comparison chains for value position ((apply < xs)).
|
||||
(define (jolt-cmp-chain op2)
|
||||
(lambda (x . xs)
|
||||
(let loop ((a x) (rest xs))
|
||||
(cond ((null? rest) #t)
|
||||
((op2 a (car rest)) (loop (car rest) (cdr rest)))
|
||||
(else #f)))))
|
||||
(define jolt-lt (jolt-cmp-chain jolt-lt2))
|
||||
(define jolt-gt (jolt-cmp-chain jolt-gt2))
|
||||
(define jolt-le (jolt-cmp-chain jolt-le2))
|
||||
(define jolt-ge (jolt-cmp-chain jolt-ge2))
|
||||
|
||||
;; call-position arithmetic: inlined macros with the both-Chez-numbers fast path
|
||||
;; open-coded; anything else falls to the binary dispatch above. Comparisons
|
||||
;; return a genuine Scheme boolean (the backend's truthy elision relies on it).
|
||||
(define-syntax jolt-n+
|
||||
(syntax-rules ()
|
||||
((_) 0)
|
||||
((_ a) (jolt-add a))
|
||||
((_ ea eb) (let ((a ea) (b eb))
|
||||
(if (and (number? a) (number? b)) (+ a b) (jolt-add a b))))
|
||||
((_ a b c ...) (jolt-n+ (jolt-n+ a b) c ...))))
|
||||
(define-syntax jolt-n-
|
||||
(syntax-rules ()
|
||||
((_) (jolt-sub))
|
||||
((_ a) (jolt-sub a))
|
||||
((_ ea eb) (let ((a ea) (b eb))
|
||||
(if (and (number? a) (number? b)) (- a b) (jolt-sub a b))))
|
||||
((_ a b c ...) (jolt-n- (jolt-n- a b) c ...))))
|
||||
(define-syntax jolt-n*
|
||||
(syntax-rules ()
|
||||
((_) 1)
|
||||
((_ a) (jolt-mul a))
|
||||
((_ ea eb) (let ((a ea) (b eb))
|
||||
(if (and (number? a) (number? b))
|
||||
(if (or (flonum? a) (flonum? b))
|
||||
(fl* (real->flonum a) (real->flonum b))
|
||||
(* a b))
|
||||
(jolt-mul a b))))
|
||||
((_ a b c ...) (jolt-n* (jolt-n* a b) c ...))))
|
||||
(define-syntax jolt-n-div
|
||||
(syntax-rules ()
|
||||
((_) (jolt-div))
|
||||
((_ a) (jolt-div a))
|
||||
((_ a b) (jolt-div2 a b))
|
||||
((_ a b c ...) (jolt-n-div (jolt-div2 a b) c ...))))
|
||||
(define-syntax define-n-cmp
|
||||
(syntax-rules ()
|
||||
((_ name op op2)
|
||||
(define-syntax name
|
||||
(syntax-rules ()
|
||||
((_) (op2))
|
||||
((_ a) (begin a #t))
|
||||
((_ ea eb) (let ((a ea) (b eb))
|
||||
(if (and (number? a) (number? b)) (op a b) (op2 a b))))
|
||||
((_ ea eb c (... ...)) (let ((a ea) (b eb))
|
||||
(and (name a b) (name b c (... ...))))))))))
|
||||
(define-n-cmp jolt-n< < jolt-lt2)
|
||||
(define-n-cmp jolt-n> > jolt-gt2)
|
||||
(define-n-cmp jolt-n<= <= jolt-le2)
|
||||
(define-n-cmp jolt-n>= >= jolt-ge2)
|
||||
(define-syntax jolt-n-min
|
||||
(syntax-rules ()
|
||||
((_) (jolt-min))
|
||||
((_ a) (jolt-min a))
|
||||
((_ a b) (jolt-min2 a b))
|
||||
((_ a b c ...) (jolt-n-min (jolt-min2 a b) c ...))))
|
||||
(define-syntax jolt-n-max
|
||||
(syntax-rules ()
|
||||
((_) (jolt-max))
|
||||
((_ a) (jolt-max a))
|
||||
((_ a b) (jolt-max2 a b))
|
||||
((_ a b c ...) (jolt-n-max (jolt-max2 a b) c ...))))
|
||||
|
||||
;; --- unchecked (Java long) arithmetic: wrap to signed 64 bits ----------------
|
||||
;; Clojure's unchecked-* (and +/-/* under *unchecked-math*) are long ops that
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue