The OP_DIV/numeric-tower work on this branch made the OCaml `/` primitive
return an exact Rational for (/ int int) (e.g. (/ 5 2)=5/2), diverging from
the canonical spec ("/ always returns inexact float"), the test-rationals.sx
header ("in the JS host, (/ int int) returns float — backward-compatible"),
and the JS host itself. That leaked rationals into arithmetic results and
rendered CSS (tw-opacity emitted `opacity:1/20` instead of `0.05`).
Decision (with the user): keep exact rationals as an explicit opt-in
(literals 1/3, make-rational) but bring `/` back into spec/host parity —
the isomorphic SSR↔hydration invariant requires both hosts to agree, and
JS has no native rational type.
- sx_primitives.ml `/`: (/ int int) → integer when exactly divisible, else
inexact float; a Rational operand still yields an exact rational (matches
test-numeric-tower: (/ 6 2)=3, (/ 1 4)=0.25, (/ 5 2)=2.5, (/ 1/2 2)=1/4).
- sx_primitives.ml number? / exact?: recognise the Rational type (real bugs —
test-rationals asserts (number? 1/3) and (exact? 1/3); inexact?/float?
already returned false for Rational, correct).
- sx_vm.ml OP_DIV: comment updated (it delegates to the now-float `/`).
- test-rationals.sx: fix typo in "rational * float = float" — used int 2,
should be 2.0 (1/2 * 2 = 1 exact, not a float; name + siblings use floats).
OCaml conformance 4834→4863 (+29 fixed, zero new failures); rationals,
numeric-tower, arithmetic, tw-opacity suites all 0 failures. Remaining run_tests
failures are the pre-existing environmental hyperscript (host-call-fn) set.
JS host already handles number?/exact? on rationals and float `/`; its
remaining float?/contagion failures are a separate pre-existing limitation
(JS has no distinct float type), out of scope here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
136 lines
5.6 KiB
Plaintext
136 lines
5.6 KiB
Plaintext
;; ==========================================================================
|
|
;; test-rationals.sx — Rational number type: literals, arithmetic, tower
|
|
;;
|
|
;; Note: in the JS host, (/ int int) returns float (backward-compatible).
|
|
;; Use rational literals (1/3, 3/4) or make-rational for exact rationals.
|
|
;; ==========================================================================
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Literals and type
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:literals"
|
|
(deftest "1/3 is rational" (assert (rational? 1/3)))
|
|
(deftest "1/2 is rational" (assert (rational? 1/2)))
|
|
(deftest "2/3 is rational" (assert (rational? 2/3)))
|
|
(deftest "literal numerator 1/3" (assert= (numerator 1/3) 1))
|
|
(deftest "literal denominator 1/3" (assert= (denominator 1/3) 3))
|
|
(deftest "literal numerator 2/3" (assert= (numerator 2/3) 2))
|
|
(deftest "auto-reduce 2/4 = 1/2" (assert= 2/4 1/2))
|
|
(deftest "auto-reduce 6/9 = 2/3" (assert= 6/9 2/3))
|
|
(deftest "negative literal" (assert= (numerator -1/3) -1)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Constructor and predicates
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:constructor"
|
|
(deftest
|
|
"make-rational basic"
|
|
(assert (rational? (make-rational 1 3))))
|
|
(deftest
|
|
"make-rational reduces"
|
|
(assert= (make-rational 2 4) 1/2))
|
|
(deftest
|
|
"make-rational exact int"
|
|
(assert (integer? (make-rational 6 3))))
|
|
(deftest
|
|
"make-rational 6/3 = 2"
|
|
(assert= (make-rational 6 3) 2))
|
|
(deftest
|
|
"make-rational negative"
|
|
(assert= (numerator (make-rational -1 3)) -1))
|
|
(deftest
|
|
"make-rational neg denom"
|
|
(assert= (numerator (make-rational 1 -3)) -1))
|
|
(deftest "rational? on int" (assert (not (rational? 5))))
|
|
(deftest "rational? on float" (assert (not (rational? 1.5))))
|
|
(deftest "rational? on string" (assert (not (rational? "1/2"))))
|
|
(deftest "number? on rational" (assert (number? 1/3)))
|
|
(deftest "exact? on rational" (assert (exact? 1/3)))
|
|
(deftest "inexact? on rational" (assert (not (inexact? 1/3))))
|
|
(deftest "integer? on rational" (assert (not (integer? 1/3))))
|
|
(deftest "dict? on rational" (assert (not (dict? 1/3)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Accessors
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:accessors"
|
|
(deftest "numerator 1/3" (assert= (numerator 1/3) 1))
|
|
(deftest "denominator 1/3" (assert= (denominator 1/3) 3))
|
|
(deftest "numerator 3/4" (assert= (numerator 3/4) 3))
|
|
(deftest "denominator 3/4" (assert= (denominator 3/4) 4))
|
|
(deftest "numerator of int" (assert= (numerator 5) 5))
|
|
(deftest
|
|
"denominator of int"
|
|
(assert= (denominator 5) 1)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Arithmetic
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:arithmetic"
|
|
(deftest "add two rationals" (assert= (+ 1/3 1/3) 2/3))
|
|
(deftest "add to integer" (assert= (+ 1 1/2) 3/2))
|
|
(deftest "add integer to rational" (assert= (+ 1/2 1) 3/2))
|
|
(deftest "add reduces" (assert= (+ 1/6 1/6) 1/3))
|
|
(deftest "add to whole number" (assert (integer? (+ 1/2 1/2))))
|
|
(deftest "add whole = 1" (assert= (+ 1/2 1/2) 1))
|
|
(deftest "subtract rationals" (assert= (- 3/4 1/4) 1/2))
|
|
(deftest "subtract int from rational" (assert= (- 3/2 1) 1/2))
|
|
(deftest "negate rational" (assert= (- 1/3) -1/3))
|
|
(deftest "multiply rationals" (assert= (* 2/3 3/4) 1/2))
|
|
(deftest "multiply int and rational" (assert= (* 2 1/3) 2/3))
|
|
(deftest "multiply reduces to int" (assert (integer? (* 3 1/3))))
|
|
(deftest "divide rational by int" (assert= (/ 2/3 2) 1/3))
|
|
(deftest "divide rational by rational" (assert= (/ 1/2 1/4) 2))
|
|
(deftest
|
|
"divide rational gives int when exact"
|
|
(assert (integer? (/ 1/2 1/2)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Float contagion
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:float-contagion"
|
|
(deftest "rational + float = float" (assert (float? (+ 1/3 0.5))))
|
|
(deftest "float + rational = float" (assert (float? (+ 0.5 1/3))))
|
|
(deftest "rational * float = float" (assert (float? (* 1/2 2.0))))
|
|
(deftest "rational - float = float" (assert (float? (- 1/2 0.1)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Comparison
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:comparison"
|
|
(deftest "equal rationals" (assert (= 1/2 1/2)))
|
|
(deftest "equal reduced" (assert (= 2/4 1/2)))
|
|
(deftest "not equal" (assert (not (= 1/3 1/2))))
|
|
(deftest "less than" (assert (< 1/3 1/2)))
|
|
(deftest "less than int" (assert (< 1/3 1)))
|
|
(deftest "greater than" (assert (> 2/3 1/2)))
|
|
(deftest "less equal" (assert (<= 1/3 1/3)))
|
|
(deftest "greater equal" (assert (>= 2/3 2/3)))
|
|
(deftest "rational less than float" (assert (< 1/3 0.5))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Coercion
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"rationals:coercion"
|
|
(deftest "exact->inexact 1/2" (assert= (exact->inexact 1/2) 0.5))
|
|
(deftest "exact->inexact 1/4" (assert= (exact->inexact 1/4) 0.25))
|
|
(deftest
|
|
"exact->inexact 1/3 is float"
|
|
(assert (float? (exact->inexact 1/3))))
|
|
(deftest "number->string 1/2" (assert= (number->string 1/2) "1/2"))
|
|
(deftest "number->string 3/4" (assert= (number->string 3/4) "3/4")))
|