Merge loops/sx-vm-extensions into architecture: rational cleanup
(/ int int) returns float per spec/host parity (was leaking exact rationals into arithmetic + CSS, e.g. tw-opacity 1/20); rational operand still yields exact rational. number?/exact? recognise Rational. test-rationals typo fix. OCaml conformance 4834->4863, rational/numeric-tower/arithmetic/tw-opacity green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -218,7 +218,14 @@ let () =
|
||||
Number (List.fold_left (fun acc a -> acc *. as_number a) 1.0 args));
|
||||
register "/" (fun args ->
|
||||
match args with
|
||||
| [Integer a; Integer b] -> make_rat a b
|
||||
(* (/ int int): exact when divisible → integer, else inexact float.
|
||||
Matches spec ("inexact float") + JS host (backward-compatible) +
|
||||
test-numeric-tower ((/ 6 2)=3, (/ 1 4)=0.25, (/ 5 2)=2.5). Exact
|
||||
rationals come ONLY from literals / make-rational, so a rational
|
||||
OPERAND keeps the result exact (cases below) — but two integers do
|
||||
NOT silently produce a rational (that diverged from the JS host). *)
|
||||
| [Integer a; Integer b] when b <> 0 && a mod b = 0 -> Integer (a / b)
|
||||
| [Integer a; Integer b] -> Number (float_of_int a /. float_of_int b)
|
||||
| [Rational(an,ad); Integer b] -> make_rat an (ad * b)
|
||||
| [Integer a; Rational(bn,bd)] -> make_rat (a * bd) bn
|
||||
| [Rational(an,ad); Rational(bn,bd)] -> rat_div (an, ad) (bn, bd)
|
||||
@@ -397,6 +404,7 @@ let () =
|
||||
register "exact?" (fun args ->
|
||||
match args with
|
||||
| [Integer _] -> Bool true
|
||||
| [Rational _] -> Bool true (* rationals are exact *)
|
||||
| [Number _] -> Bool false
|
||||
| [_] -> Bool false
|
||||
| _ -> raise (Eval_error "exact?: 1 arg"));
|
||||
@@ -833,7 +841,7 @@ let () =
|
||||
match args with [a] -> Bool (is_nil a) | _ -> raise (Eval_error "nil?: 1 arg"));
|
||||
register "number?" (fun args ->
|
||||
match args with
|
||||
| [Integer _] | [Number _] -> Bool true
|
||||
| [Integer _] | [Number _] | [Rational _] -> Bool true
|
||||
| [_] -> Bool false
|
||||
| _ -> raise (Eval_error "number?: 1 arg"));
|
||||
register "integer?" (fun args ->
|
||||
|
||||
@@ -829,10 +829,10 @@ and run vm =
|
||||
let b = pop vm and a = pop vm in
|
||||
push vm (match a, b with
|
||||
| Integer x, Integer y when y <> 0 && x mod y = 0 -> Integer (x / y)
|
||||
(* Non-divisible Integer/Integer must delegate to the "/" primitive:
|
||||
it returns an exact Rational (e.g. 1/2), matching CEK semantics.
|
||||
Inlining float division here (0.5) diverges from the interpreter
|
||||
and breaks numeric equality against rational results. *)
|
||||
(* Non-divisible Integer/Integer + any Rational operand delegate to
|
||||
the "/" primitive (single source of truth): (/ 5 2)=2.5 float,
|
||||
(/ 1/2 2)=1/4 rational. Keeping the VM in lockstep with the
|
||||
primitive avoids diverging from the CEK interpreter. *)
|
||||
| Number x, Number y -> Number (x /. y)
|
||||
| Integer x, Number y -> Number (float_of_int x /. y)
|
||||
| Number x, Integer y -> Number (x /. float_of_int y)
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
"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))))
|
||||
(deftest "rational * float = float" (assert (float? (* 1/2 2.0))))
|
||||
(deftest "rational - float = float" (assert (float? (- 1/2 0.1)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user