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:
@@ -61,6 +61,7 @@ let all_ints = List.for_all (function Integer _ -> true | _ -> false)
|
||||
let rec as_number = function
|
||||
| Integer n -> float_of_int n
|
||||
| Number n -> n
|
||||
| Rational(n, d) -> float_of_int n /. float_of_int d
|
||||
| Bool true -> 1.0
|
||||
| Bool false -> 0.0
|
||||
| Nil -> 0.0
|
||||
@@ -101,32 +102,86 @@ let rec to_string = function
|
||||
|
||||
let gensym_counter = ref 0
|
||||
|
||||
let rat_gcd a b =
|
||||
let rec g a b = if b = 0 then a else g b (a mod b) in g (abs a) (abs b)
|
||||
|
||||
let make_rat n d =
|
||||
if d = 0 then raise (Eval_error "rational: division by zero");
|
||||
let sign = if d < 0 then -1 else 1 in
|
||||
let g = rat_gcd (abs n) (abs d) in
|
||||
let rn = sign * n / g and rd = sign * d / g in
|
||||
if rd = 1 then Integer rn else Rational (rn, rd)
|
||||
|
||||
let rat_of_val = function
|
||||
| Integer n -> (n, 1)
|
||||
| Rational(n,d) -> (n, d)
|
||||
| v -> raise (Eval_error ("expected integer or rational, got " ^ type_of v))
|
||||
|
||||
let has_rational args = List.exists (function Rational _ -> true | _ -> false) args
|
||||
let has_float args = List.exists (function Number _ -> true | _ -> false) args
|
||||
|
||||
let rat_add (an, ad) (bn, bd) = make_rat (an * bd + bn * ad) (ad * bd)
|
||||
let rat_sub (an, ad) (bn, bd) = make_rat (an * bd - bn * ad) (ad * bd)
|
||||
let rat_mul (an, ad) (bn, bd) = make_rat (an * bn) (ad * bd)
|
||||
let rat_div (an, ad) (bn, bd) =
|
||||
if bn = 0 then raise (Eval_error "rational: division by zero");
|
||||
make_rat (an * bd) (ad * bn)
|
||||
|
||||
let () =
|
||||
(* === Arithmetic === *)
|
||||
register "+" (fun args ->
|
||||
if all_ints args then
|
||||
Integer (List.fold_left (fun acc a -> match a with Integer n -> acc + n | _ -> acc) 0 args)
|
||||
else if has_rational args && not (has_float args) then
|
||||
List.fold_left (fun acc a ->
|
||||
match acc, a with
|
||||
| Integer an, _ -> rat_add (an, 1) (rat_of_val a)
|
||||
| Rational(an,ad), _ -> rat_add (an, ad) (rat_of_val a)
|
||||
| _ -> acc
|
||||
) (Integer 0) args
|
||||
else
|
||||
Number (List.fold_left (fun acc a -> acc +. as_number a) 0.0 args));
|
||||
register "-" (fun args ->
|
||||
match args with
|
||||
| [] -> Integer 0
|
||||
| [Integer n] -> Integer (-n)
|
||||
| [Rational(n,d)] -> make_rat (-n) d
|
||||
| [a] -> Number (-. (as_number a))
|
||||
| _ when all_ints args ->
|
||||
(match args with
|
||||
| Integer h :: tl ->
|
||||
Integer (List.fold_left (fun acc a -> match a with Integer n -> acc - n | _ -> acc) h tl)
|
||||
| _ -> Number 0.0)
|
||||
| _ when has_rational args && not (has_float args) ->
|
||||
(match args with
|
||||
| h :: tl ->
|
||||
List.fold_left (fun acc a ->
|
||||
match acc with
|
||||
| Integer an -> rat_sub (an, 1) (rat_of_val a)
|
||||
| Rational(an,ad) -> rat_sub (an, ad) (rat_of_val a)
|
||||
| _ -> acc
|
||||
) h tl
|
||||
| _ -> Integer 0)
|
||||
| a :: rest ->
|
||||
Number (List.fold_left (fun acc x -> acc -. as_number x) (as_number a) rest));
|
||||
register "*" (fun args ->
|
||||
if all_ints args then
|
||||
Integer (List.fold_left (fun acc a -> match a with Integer n -> acc * n | _ -> acc) 1 args)
|
||||
else if has_rational args && not (has_float args) then
|
||||
List.fold_left (fun acc a ->
|
||||
match acc with
|
||||
| Integer an -> rat_mul (an, 1) (rat_of_val a)
|
||||
| Rational(an,ad) -> rat_mul (an, ad) (rat_of_val a)
|
||||
| _ -> acc
|
||||
) (Integer 1) args
|
||||
else
|
||||
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
|
||||
| [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)
|
||||
| [a; b] -> Number (as_number a /. as_number b)
|
||||
| _ -> raise (Eval_error "/: expected 2 args"));
|
||||
register "mod" (fun args ->
|
||||
@@ -315,6 +370,7 @@ let () =
|
||||
match args with
|
||||
| [Integer n] -> Number (float_of_int n)
|
||||
| [Number n] -> Number n
|
||||
| [Rational(n,d)] -> Number (float_of_int n /. float_of_int d)
|
||||
| [a] -> Number (as_number a)
|
||||
| _ -> raise (Eval_error "exact->inexact: 1 arg"));
|
||||
register "inexact->exact" (fun args ->
|
||||
@@ -371,6 +427,7 @@ let () =
|
||||
match args with
|
||||
| [Integer n] -> String (string_of_int n)
|
||||
| [Number f] -> String (Printf.sprintf "%g" f)
|
||||
| [Rational(n,d)] -> String (Printf.sprintf "%d/%d" n d)
|
||||
| [Integer n; Integer r] ->
|
||||
if r < 2 || r > 36 then raise (Eval_error "number->string: radix out of range");
|
||||
String (int_to_radix n r)
|
||||
@@ -402,6 +459,35 @@ let () =
|
||||
Integer (if neg then - !n else !n)
|
||||
with _ -> Nil)
|
||||
| _ -> raise (Eval_error "string->number: 1-2 args"));
|
||||
let make_rational_val n d =
|
||||
if d = 0 then raise (Eval_error "make-rational: denominator cannot be zero");
|
||||
let rec gcd a b = if b = 0 then a else gcd b (a mod b) in
|
||||
let sign = if d < 0 then -1 else 1 in
|
||||
let g = gcd (abs n) (abs d) in
|
||||
let rn = sign * n / g and rd = sign * d / g in
|
||||
if rd = 1 then Integer rn else Rational (rn, rd)
|
||||
in
|
||||
register "make-rational" (fun args ->
|
||||
match args with
|
||||
| [Integer n; Integer d] -> make_rational_val n d
|
||||
| [Number f; Integer d] -> make_rational_val (int_of_float f) d
|
||||
| [Integer n; Number f] -> make_rational_val n (int_of_float f)
|
||||
| _ -> raise (Eval_error "make-rational: expected 2 integers"));
|
||||
register "rational?" (fun args ->
|
||||
match args with
|
||||
| [Rational _] -> Bool true
|
||||
| [_] -> Bool false
|
||||
| _ -> raise (Eval_error "rational?: expected 1 arg"));
|
||||
register "numerator" (fun args ->
|
||||
match args with
|
||||
| [Rational (n, _)] -> Integer n
|
||||
| [Integer n] -> Integer n
|
||||
| _ -> raise (Eval_error "numerator: expected rational or integer"));
|
||||
register "denominator" (fun args ->
|
||||
match args with
|
||||
| [Rational (_, d)] -> Integer d
|
||||
| [Integer _] -> Integer 1
|
||||
| _ -> raise (Eval_error "denominator: expected rational or integer"));
|
||||
register "parse-int" (fun args ->
|
||||
let parse_leading_int s =
|
||||
let len = String.length s in
|
||||
@@ -442,6 +528,11 @@ let () =
|
||||
| Number x, Number y -> x = y
|
||||
| Integer x, Number y -> float_of_int x = y
|
||||
| Number x, Integer y -> x = float_of_int y
|
||||
| Rational(n, d), Number y -> float_of_int n /. float_of_int d = y
|
||||
| Number x, Rational(n, d) -> x = float_of_int n /. float_of_int d
|
||||
| Rational(an, ad), Rational(bn, bd) -> an * bd = bn * ad
|
||||
| Rational(n, d), Integer y -> n = y * d
|
||||
| Integer x, Rational(n, d) -> x * d = n
|
||||
| String x, String y -> x = y
|
||||
| Bool x, Bool y -> x = y
|
||||
| Nil, Nil -> true
|
||||
|
||||
@@ -78,6 +78,7 @@ and value =
|
||||
| Char of int (** Unicode codepoint — R7RS char type. *)
|
||||
| Eof (** EOF sentinel — returned by read-char etc. at end of input. *)
|
||||
| Port of sx_port (** String port — input (string cursor) or output (buffer). *)
|
||||
| Rational of int * int (** Exact rational: numerator, denominator (reduced, denom>0). *)
|
||||
|
||||
(** String input port: source string + mutable cursor position. *)
|
||||
and sx_port_kind =
|
||||
@@ -512,6 +513,7 @@ let type_of = function
|
||||
| Eof -> "eof-object"
|
||||
| Port { sp_kind = PortInput _; _ } -> "input-port"
|
||||
| Port { sp_kind = PortOutput _; _ } -> "output-port"
|
||||
| Rational _ -> "rational"
|
||||
|
||||
let is_nil = function Nil -> true | _ -> false
|
||||
let is_lambda = function Lambda _ -> true | _ -> false
|
||||
@@ -873,3 +875,4 @@ let rec inspect = function
|
||||
Printf.sprintf "<input-port:pos=%d%s>" !pos (if sp_closed then ":closed" else "")
|
||||
| Port { sp_kind = PortOutput buf; sp_closed } ->
|
||||
Printf.sprintf "<output-port:len=%d%s>" (Buffer.length buf) (if sp_closed then ":closed" else "")
|
||||
| Rational (n, d) -> Printf.sprintf "%d/%d" n d
|
||||
|
||||
Reference in New Issue
Block a user