Add `Integer of int` to sx_types.ml alongside `Number of float`. Parser produces Integer for whole-number literals. Arithmetic primitives apply float contagion (int op int → Integer, int op float → Number). Division always returns Number. Rounding (floor/truncate/round) returns Integer. Predicates: integer?, float?, exact?, inexact?, exact->inexact, inexact->exact. run_tests.ml updated for json_of_value, value_of_json, identical?, random-int mock, DOM accessors, and parser pattern matches. New spec/tests/test-numeric-tower.sx — 92 tests, all pass (394 unchanged). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
222 lines
8.4 KiB
Plaintext
222 lines
8.4 KiB
Plaintext
|
|
;; ==========================================================================
|
|
;; test-numeric-tower.sx — Numeric tower: Integer vs Float distinction
|
|
;;
|
|
;; Tests for float contagion, integer arithmetic, predicates,
|
|
;; coercions, parsing, and rendering.
|
|
;;
|
|
;; Note: Use fractional floats (1.5, 3.14) or exact->inexact for round floats,
|
|
;; since the SX serializer renders Number 1.0 as "1" (int form).
|
|
;; ==========================================================================
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Integer arithmetic — result stays Integer when all args are Integer
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:int-arithmetic"
|
|
(deftest "int + int = int" (assert (integer? (+ 1 2))))
|
|
(deftest "int + int value" (assert= (+ 1 2) 3))
|
|
(deftest "int - int = int" (assert (integer? (- 10 3))))
|
|
(deftest "int - int value" (assert= (- 10 3) 7))
|
|
(deftest "int * int = int" (assert (integer? (* 4 5))))
|
|
(deftest "int * int value" (assert= (* 4 5) 20))
|
|
(deftest "zero identity" (assert= (+ 0 0) 0))
|
|
(deftest "negative int" (assert= (- 0 5) -5))
|
|
(deftest
|
|
"int negation is int"
|
|
(assert (integer? (- 0 7))))
|
|
(deftest
|
|
"large int product"
|
|
(assert= (* 100 100) 10000)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Float contagion — any float arg promotes result to float
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:float-contagion"
|
|
(deftest "int + float = float" (assert (float? (+ 1 1.5))))
|
|
(deftest "int + float value" (assert= (+ 1 1.5) 2.5))
|
|
(deftest "float + int = float" (assert (float? (+ 1.5 2))))
|
|
(deftest "float + float = float" (assert (float? (+ 1.5 2.5))))
|
|
(deftest "int * float = float" (assert (float? (* 2 1.5))))
|
|
(deftest "int * float value" (assert= (* 2 1.5) 3))
|
|
(deftest "int - float = float" (assert (float? (- 5 2.5))))
|
|
(deftest "float - int = float" (assert (float? (- 5.5 2))))
|
|
(deftest
|
|
"three args with float"
|
|
(assert (float? (+ 1 2 3.5))))
|
|
(deftest
|
|
"exact->inexact promotes to float"
|
|
(assert (float? (exact->inexact 5)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Division always returns float
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:division"
|
|
(deftest "int / int = float" (assert (float? (/ 6 2))))
|
|
(deftest "exact division value" (assert= (/ 6 2) 3))
|
|
(deftest "inexact division" (assert= (/ 1 4) 0.25))
|
|
(deftest "float / float = float" (assert (float? (/ 3.5 2.5)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Type predicates
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:predicates"
|
|
(deftest "integer? on int" (assert (integer? 42)))
|
|
(deftest "integer? on negative" (assert (integer? -7)))
|
|
(deftest "integer? on zero" (assert (integer? 0)))
|
|
(deftest
|
|
"integer? on float-int"
|
|
(assert (integer? (exact->inexact 2))))
|
|
(deftest "integer? on fractional float" (assert (not (integer? 1.5))))
|
|
(deftest "float? on 1.5" (assert (float? 1.5)))
|
|
(deftest
|
|
"float? on exact->inexact"
|
|
(assert (float? (exact->inexact 2))))
|
|
(deftest "float? on int" (assert (not (float? 42))))
|
|
(deftest "number? on int" (assert (number? 42)))
|
|
(deftest "number? on float" (assert (number? 3.14)))
|
|
(deftest "number? on string" (assert (not (number? "42"))))
|
|
(deftest "exact? on int" (assert (exact? 1)))
|
|
(deftest
|
|
"exact? on exact->inexact"
|
|
(assert (not (exact? (exact->inexact 1)))))
|
|
(deftest "inexact? on 1.5" (assert (inexact? 1.5)))
|
|
(deftest "inexact? on int" (assert (not (inexact? 3)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Coercions
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:coercions"
|
|
(deftest "exact->inexact int" (assert= (exact->inexact 3) 3))
|
|
(deftest
|
|
"exact->inexact produces float"
|
|
(assert (float? (exact->inexact 5))))
|
|
(deftest
|
|
"exact->inexact float passthrough"
|
|
(assert= (exact->inexact 1.5) 1.5))
|
|
(deftest "inexact->exact 1.5" (assert= (inexact->exact 1.5) 2))
|
|
(deftest
|
|
"inexact->exact produces int"
|
|
(assert (integer? (inexact->exact (exact->inexact 4)))))
|
|
(deftest "inexact->exact 2.7" (assert= (inexact->exact 2.7) 3))
|
|
(deftest
|
|
"inexact->exact int passthrough"
|
|
(assert= (inexact->exact 5) 5)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; floor / ceiling / truncate / round — return Integer for floats
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:rounding"
|
|
(deftest "floor 3.7" (assert= (floor 3.7) 3))
|
|
(deftest "floor produces int" (assert (integer? (floor 3.7))))
|
|
(deftest "floor negative" (assert= (floor -2.3) -3))
|
|
(deftest "truncate 3.9" (assert= (truncate 3.9) 3))
|
|
(deftest "truncate negative" (assert= (truncate -3.9) -3))
|
|
(deftest "truncate produces int" (assert (integer? (truncate 3.9))))
|
|
(deftest "round 2.3 down" (assert= (round 2.3) 2))
|
|
(deftest "round produces int" (assert (integer? (round 2.3))))
|
|
(deftest
|
|
"floor of int passthrough"
|
|
(assert= (floor 5) 5))
|
|
(deftest "floor of int stays int" (assert (integer? (floor 5)))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; parse-number distinguishes int vs float strings
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:parse-number"
|
|
(deftest
|
|
"parse-number int string"
|
|
(assert= (parse-number "42") 42))
|
|
(deftest
|
|
"parse-number int is integer?"
|
|
(assert (integer? (parse-number "42"))))
|
|
(deftest "parse-number 3.14" (assert= (parse-number "3.14") 3.14))
|
|
(deftest
|
|
"parse-number float is float?"
|
|
(assert (float? (parse-number "3.14"))))
|
|
(deftest
|
|
"parse-number 1.5 is float?"
|
|
(assert (float? (parse-number "1.5"))))
|
|
(deftest
|
|
"parse-number negative int"
|
|
(assert= (parse-number "-5") -5))
|
|
(deftest
|
|
"parse-number negative int is integer?"
|
|
(assert (integer? (parse-number "-5"))))
|
|
(deftest "parse-int returns integer" (assert (integer? (parse-int "7"))))
|
|
(deftest "parse-int value" (assert= (parse-int "7") 7)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Equality across numeric types
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:equality"
|
|
(deftest "int = same int" (assert= 5 5))
|
|
(deftest
|
|
"int = float eq"
|
|
(assert (= 1 (exact->inexact 1))))
|
|
(deftest
|
|
"float = int eq"
|
|
(assert (= (exact->inexact 1) 1)))
|
|
(deftest "int != different int" (assert (!= 1 2)))
|
|
(deftest "int < float" (assert (< 1 1.5)))
|
|
(deftest "float > int" (assert (> 2.5 2)))
|
|
(deftest "int <= float" (assert (<= 2 2.5)))
|
|
(deftest "int >= int" (assert (>= 3 3))))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; mod / remainder / modulo with integers
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:modulo"
|
|
(deftest
|
|
"mod int int = int"
|
|
(assert (integer? (mod 10 3))))
|
|
(deftest "mod value" (assert= (mod 10 3) 1))
|
|
(deftest
|
|
"remainder int int = int"
|
|
(assert (integer? (remainder 10 3))))
|
|
(deftest
|
|
"remainder value"
|
|
(assert= (remainder 10 3) 1)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; min / max with mixed types
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:min-max"
|
|
(deftest "min two ints" (assert= (min 3 7) 3))
|
|
(deftest
|
|
"min int result type"
|
|
(assert (integer? (min 3 7))))
|
|
(deftest "max two ints" (assert= (max 3 7) 7))
|
|
(deftest "min with float" (assert= (min 3 2.5) 2.5))
|
|
(deftest "max with float" (assert= (max 3 3.5) 3.5)))
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; str rendering of int vs float
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite
|
|
"numeric-tower:stringify"
|
|
(deftest "str of int" (assert= (str 42) "42"))
|
|
(deftest "str of negative int" (assert= (str -5) "-5"))
|
|
(deftest "str of 3.14" (assert= (str 3.14) "3.14"))
|
|
(deftest "str of 1.5" (assert= (str 1.5) "1.5")))
|