spec: rational numbers — 1/3 literals, arithmetic, numeric tower integration

SxRational type in OCaml (Rational of int * int, stored reduced, denom>0)
and JS (SxRational class with _rational marker). n/d reader syntax in
spec/parser.sx. Arithmetic contagion: int op rational → rational, rational
op float → float. JS keeps int/int → float for CSS backward compatibility.
OCaml as_number + safe_eq extended for cross-type rational equality so
(= 2.5 5/2) → true. 62 tests in test-rationals.sx, all pass.
JS: 2232 passed. OCaml: 4532 passed (+11 vs pre-fix baseline).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 17:27:27 +00:00
parent e9d2003d6a
commit 036022cc17
12 changed files with 1558 additions and 859 deletions

View File

@@ -1,4 +1,3 @@
;; ==========================================================================
;; test-numeric-tower.sx — Numeric tower: Integer vs Float distinction
;;
@@ -52,15 +51,20 @@
(assert (float? (exact->inexact 5)))))
;; --------------------------------------------------------------------------
;; Division always returns float
;; Division
;; --------------------------------------------------------------------------
(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)))))
(deftest
"exact division value"
(assert= (/ 6 2) 3))
(deftest "inexact division value" (assert= (/ 1 4) 0.25))
(deftest "float / float = float" (assert (float? (/ 3.5 2.5))))
(deftest
"rational / int = rational"
(assert (rational? (/ 1/2 2))))
(deftest "rational division value" (assert= (/ 1/2 2) 1/4)))
;; --------------------------------------------------------------------------
;; Type predicates
@@ -82,8 +86,10 @@
(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 rational" (assert (number? 1/3)))
(deftest "number? on string" (assert (not (number? "42"))))
(deftest "exact? on int" (assert (exact? 1)))
(deftest "exact? on rational" (assert (exact? 1/3)))
(deftest
"exact? on exact->inexact"
(assert (not (exact? (exact->inexact 1)))))
@@ -96,13 +102,16 @@
(defsuite
"numeric-tower:coercions"
(deftest "exact->inexact int" (assert= (exact->inexact 3) 3))
(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 "exact->inexact rational" (assert= (exact->inexact 1/4) 0.25))
(deftest "inexact->exact 1.5" (assert= (inexact->exact 1.5) 2))
(deftest
"inexact->exact produces int"