byte/short/int/long/char silently wrapped or passed out-of-range values through; the JVM range-checks (RT.byteCast family). One checked-cast helper now carries the ranges: a double range-checks ITSELF before truncating ((byte 1.1) is 1, (byte 127.000001) throws), NaN casts to 0, ratios and bigdecs truncate, a non-number is CCE, and the throw carries the JVM message. float range-checks against Float/MAX_VALUE. The unchecked-* casts now genuinely wrap and sign-fold ((unchecked-byte 200) is -56 — the old bit-and lost the sign) with doubles saturating like Java's conversions; unchecked-long/int are host natives. double/float of a bigdec convert instead of crashing. The no-single-float residue stays accepted (SPEC.md). Also fixes #290: a binary built by the SELF-CONTAINED joltc died with 'variable var-deref is not bound' when a namespace loaded at runtime. The in-process build compiled flat.ss against a clean copy-environment, which orphans every top-level define in locations the binary's runtime eval can't see. It now compiles against the default interaction environment (defines land in the real symbol cells, same as the legacy fresh-Chez path) and a generated prologue pre-binds each kernel name the runtime redefines to its kernel value, so the earliest boot reads match the legacy path's primitive references. requiring-resolve is implemented (the issue's dynamic-require pattern), and the release workflow smokes a runtime require in a built binary. Cast namespaces byte/short/int/long/char now fully clean; cts baseline 5805 -> 5857 pass, 67 baselined namespaces. 7 JVM-certified corpus rows.
397 lines
20 KiB
Scheme
397 lines
20 KiB
Scheme
;; BigDecimal. A jbigdec is {unscaled, scale} over Chez arbitrary-precision exact
|
|
;; integers; its value is unscaled * 10^-scale (1.5M = {15,1}, 1.00M = {100,2},
|
|
;; 3M = {3,0}). M-suffix literals read to a :bigdec form that the back end lowers
|
|
;; to jolt-bigdec-from-string; bigdec coerces a number/string. Equality is by
|
|
;; value (1.0M = 1.00M), str drops the M, pr keeps it, class is
|
|
;; java.math.BigDecimal.
|
|
;;
|
|
;; 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 (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))
|
|
|
|
(define (bd-index-char s ch)
|
|
(let loop ((i 0))
|
|
(cond ((>= i (string-length s)) #f)
|
|
((char=? (string-ref s i) ch) i)
|
|
(else (loop (+ i 1))))))
|
|
|
|
;; "1.50" -> {150,2}; "3" -> {3,0}; "-0.0" -> {0,1}; ".5" -> {5,1}.
|
|
(define (jolt-bigdec-from-string s)
|
|
(let* ((neg (and (> (string-length s) 0) (char=? (string-ref s 0) #\-)))
|
|
(sgn (and (> (string-length s) 0) (or neg (char=? (string-ref s 0) #\+))))
|
|
(s1 (if sgn (substring s 1 (string-length s)) s))
|
|
(sign (if neg -1 1))
|
|
(dot (bd-index-char s1 #\.)))
|
|
(if dot
|
|
(let* ((intp (substring s1 0 dot))
|
|
(fracp (substring s1 (+ dot 1) (string-length s1)))
|
|
(digs (string-append intp fracp))
|
|
(unscaled (if (= 0 (string-length digs)) 0 (string->number digs))))
|
|
(make-jbigdec (* sign unscaled) (string-length fracp)))
|
|
(make-jbigdec (* sign (string->number s1)) 0))))
|
|
|
|
;; bigdec coercion: a bigdec is itself; an exact integer keeps scale 0; a string
|
|
;; or any other number routes through its decimal text.
|
|
(define (jolt-bigdec x)
|
|
(cond
|
|
((jbigdec? x) x)
|
|
((and (number? x) (exact? x) (integer? x)) (make-jbigdec x 0))
|
|
((string? x) (jolt-bigdec-from-string x))
|
|
((number? x) (jolt-bigdec-from-string (jolt-num->string x)))
|
|
(else (error #f "bigdec: cannot coerce" x))))
|
|
|
|
;; value equality: unscaled_a * 10^scale_b == unscaled_b * 10^scale_a.
|
|
(define (jbigdec=? a b)
|
|
(= (* (jbigdec-unscaled a) (expt 10 (jbigdec-scale b)))
|
|
(* (jbigdec-unscaled b) (expt 10 (jbigdec-scale a)))))
|
|
|
|
;; render the decimal text (no M): insert the point `scale` digits from the right.
|
|
(define (jbigdec->string bd)
|
|
(let* ((u (jbigdec-unscaled bd)) (sc (jbigdec-scale bd))
|
|
(neg (< u 0)) (digs (number->string (abs u))))
|
|
(string-append
|
|
(if neg "-" "")
|
|
(if (<= sc 0)
|
|
digs
|
|
(let* ((padded (if (<= (string-length digs) sc)
|
|
(string-append (make-string (- (+ sc 1) (string-length digs)) #\0) digs)
|
|
digs))
|
|
(pl (string-length padded)))
|
|
(string-append (substring padded 0 (- pl sc)) "." (substring padded (- pl sc) pl)))))))
|
|
|
|
;; value as a Chez flonum (for double contagion: a flonum operand wins).
|
|
(define (jbigdec->flonum b)
|
|
(exact->inexact (/ (jbigdec-unscaled b) (expt 10 (jbigdec-scale b)))))
|
|
|
|
;; 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 --------------------------
|
|
;; align two bigdecs to a common scale, returning (unscaled-a unscaled-b scale).
|
|
(define (jbd-align a b)
|
|
(let ((sa (jbigdec-scale a)) (sb (jbigdec-scale b)))
|
|
(cond
|
|
((= sa sb) (values (jbigdec-unscaled a) (jbigdec-unscaled b) sa))
|
|
((> sa sb) (values (jbigdec-unscaled a)
|
|
(* (jbigdec-unscaled b) (expt 10 (- sa sb))) sa))
|
|
(else (values (* (jbigdec-unscaled a) (expt 10 (- sb sa)))
|
|
(jbigdec-unscaled b) sb)))))
|
|
|
|
(define (jbd2+ a b) (let-values (((ua ub s) (jbd-align a b))) (make-jbigdec (+ ua ub) s)))
|
|
(define (jbd2- a b) (let-values (((ua ub s) (jbd-align a b))) (make-jbigdec (- ua ub) s)))
|
|
(define (jbd2* a b) (make-jbigdec (* (jbigdec-unscaled a) (jbigdec-unscaled b))
|
|
(+ (jbigdec-scale a) (jbigdec-scale b))))
|
|
(define (jbd-negate a) (make-jbigdec (- (jbigdec-unscaled a)) (jbigdec-scale a)))
|
|
|
|
;; exact rational -> bigdec at minimal scale, or throw if non-terminating. den must
|
|
;; factor into 2s and 5s; scale = max(count2, count5).
|
|
(define (jbd-rational->bigdec r)
|
|
(let ((p (numerator r)) (q (denominator r)))
|
|
(let loop ((d q) (c2 0) (c5 0))
|
|
(cond
|
|
((= d 1) (let ((sc (max c2 c5)))
|
|
(make-jbigdec (* p (quotient (expt 10 sc) q)) sc)))
|
|
((= 0 (modulo d 2)) (loop (quotient d 2) (+ c2 1) c5))
|
|
((= 0 (modulo d 5)) (loop (quotient d 5) c2 (+ c5 1)))
|
|
(else (jolt-throw (jolt-host-throwable
|
|
"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. 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)
|
|
(when (= 0 (jbigdec-unscaled b))
|
|
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Divide by zero")))
|
|
(let-values (((ua ub s) (jbd-align a b))) (make-jbigdec (quotient ua ub) 0)))
|
|
(define (jbd-int-rem a b)
|
|
(when (= 0 (jbigdec-unscaled b))
|
|
(jolt-throw (jolt-host-throwable "java.lang.ArithmeticException" "Divide by zero")))
|
|
(let-values (((ua ub s) (jbd-align a b)))
|
|
(make-jbigdec (remainder ua ub) (max (jbigdec-scale a) (jbigdec-scale b)))))
|
|
|
|
;; scale-independent ordering: compare unscaled values aligned to a common scale.
|
|
(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
|
|
;; (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))
|
|
(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)
|
|
(let loop ((acc init) (rest xs))
|
|
(if (null? rest) acc (loop (jbd-binop flonum-op bd-op acc (car rest)) (cdr rest)))))
|
|
|
|
(define (jbd-add . xs)
|
|
(cond ((null? xs) (make-jbigdec 0 0))
|
|
((null? (cdr xs)) (car xs))
|
|
(else (jbd-fold + jbd2+ (car xs) (cdr xs)))))
|
|
(define (jbd-sub . xs)
|
|
(cond ((null? xs) (error #f "- needs at least 1 arg"))
|
|
((null? (cdr xs)) (if (jbigdec? (car xs)) (jbd-negate (car xs)) (- (car xs))))
|
|
(else (jbd-fold - jbd2- (car xs) (cdr xs)))))
|
|
(define (jbd-mul . xs)
|
|
(cond ((null? xs) (make-jbigdec 1 0))
|
|
((null? (cdr xs)) (car xs))
|
|
(else (jbd-fold * jbd2* (car xs) (cdr xs)))))
|
|
(define (jbd-div . xs)
|
|
(cond ((null? xs) (error #f "/ needs at least 1 arg"))
|
|
((null? (cdr xs)) (jbd-binop / jbd2-div (make-jbigdec 1 0) (car xs)))
|
|
(else (jbd-fold / jbd2-div (car xs) (cdr xs)))))
|
|
|
|
;; comparison / predicate helpers (Phase-2 emit targets). A flonum operand demotes
|
|
;; to the native comparison on the flonum values.
|
|
(define (jbd-cmp-num op flop a b)
|
|
(if (or (flonum? a) (flonum? b))
|
|
(flop (if (jbigdec? a) (jbigdec->flonum a) a) (if (jbigdec? b) (jbigdec->flonum b) b))
|
|
(op (jbd-compare2 (jbd-coerce a) (jbd-coerce b)) 0)))
|
|
(define (jbd-lt? a b) (jbd-cmp-num < < a b))
|
|
(define (jbd-gt? a b) (jbd-cmp-num > > a b))
|
|
(define (jbd-le? a b) (jbd-cmp-num <= <= a b))
|
|
(define (jbd-ge? a b) (jbd-cmp-num >= >= a b))
|
|
(define (jbd-zero? a) (= 0 (jbigdec-unscaled a)))
|
|
(define (jbd-pos? a) (> (jbigdec-unscaled a) 0))
|
|
(define (jbd-neg? a) (< (jbigdec-unscaled a) 0))
|
|
(define (jbd-quot a b) (jbd-int-quot (jbd-coerce a) (jbd-coerce b)))
|
|
(define (jbd-rem a b) (jbd-int-rem (jbd-coerce a) (jbd-coerce b)))
|
|
|
|
;; min/max compare by value but return the ORIGINAL operand (its type and scale
|
|
;; unchanged), matching java/Clojure: (min 1M 2.0) -> 1M, (max 1M 2.0) -> 2.0,
|
|
;; (min 1.50M 2M) -> 1.50M. Comparison handles a bigdec mixed with an int / flonum.
|
|
(define (jbd-value-compare a b)
|
|
(if (or (flonum? a) (flonum? b))
|
|
(let ((fa (if (jbigdec? a) (jbigdec->flonum a) a)) (fb (if (jbigdec? b) (jbigdec->flonum b) b)))
|
|
(cond ((< fa fb) -1) ((> fa fb) 1) (else 0)))
|
|
(jbd-compare2 (jbd-coerce a) (jbd-coerce b))))
|
|
;; strict comparison so a tie keeps the second operand, like Clojure's
|
|
;; (if (< x y) x y) / (if (> x y) x y): (max 1.5M 1.50M) -> 1.50M.
|
|
(define (jbd-min2 a b) (if (< (jbd-value-compare a b) 0) a b))
|
|
(define (jbd-max2 a b) (if (> (jbd-value-compare a b) 0) a b))
|
|
(define (jbd-min x . xs) (fold-left jbd-min2 x xs))
|
|
(define (jbd-max x . xs) (fold-left jbd-max2 x xs))
|
|
|
|
;; --- wire into the value model ----------------------------------------------
|
|
(def-var! "clojure.core" "bigdec" jolt-bigdec)
|
|
|
|
;; 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)
|
|
|
|
;; double/float of a bigdec is its flonum value.
|
|
(set! jolt-double-slow
|
|
(let ((prev jolt-double-slow))
|
|
(lambda (x) (if (jbigdec? x) (jbigdec->flonum x) (prev x)))))
|
|
|
|
;; narrow casts truncate a bigdec like Number.longValue.
|
|
(set! jolt-cast-truncate-slow
|
|
(let ((prev jolt-cast-truncate-slow))
|
|
(lambda (x)
|
|
(if (jbigdec? x)
|
|
(truncate (/ (jbigdec-unscaled x) (expt 10 (jbigdec-scale x))))
|
|
(prev x)))))
|
|
|
|
;; 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.
|
|
(define jbd-prev-compare jolt-compare)
|
|
(define (jbd-numberish? x) (or (jbigdec? x) (number? x)))
|
|
(set! jolt-compare
|
|
(lambda (a b)
|
|
(if (and (or (jbigdec? a) (jbigdec? b)) (jbd-numberish? a) (jbd-numberish? b))
|
|
(if (or (flonum? a) (flonum? b))
|
|
(let ((fa (if (jbigdec? a) (jbigdec->flonum a) a))
|
|
(fb (if (jbigdec? b) (jbigdec->flonum b) b)))
|
|
(cond ((< fa fb) -1) ((> fa fb) 1) (else 0)))
|
|
(jbd-compare2 (jbd-coerce a) (jbd-coerce b)))
|
|
(jbd-prev-compare a b))))
|
|
(def-var! "clojure.core" "compare" jolt-compare)
|
|
|
|
;; equality: a bigdec equals only another bigdec, by value (matching (= 3M 3) = false).
|
|
(register-eq-arm! (lambda (a b) (or (jbigdec? a) (jbigdec? b)))
|
|
(lambda (a b) (and (jbigdec? a) (jbigdec? b) (jbigdec=? a b))))
|
|
|
|
;; str drops the M; pr/pr-str keep it.
|
|
(register-str-render! jbigdec? jbigdec->string)
|
|
(register-pr-arm! jbigdec? (lambda (x) (string-append (jbigdec->string x) "M")))
|
|
|
|
;; class / decimal?
|
|
(register-class-arm! jbigdec? (lambda (x) "java.math.BigDecimal"))
|
|
(set! jolt-decimal? (lambda (x) (jbigdec? x)))
|
|
(def-var! "clojure.core" "decimal?" jolt-decimal?)
|