spec: math completeness — trig, quotient, gcd/lcm, radix number<->string
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Phase 15 implementation: - spec/primitives.sx: stdlib.math module — sin/cos/tan/asin/acos/atan/exp/log/expt/quotient/gcd/lcm/number->string/string->number (13 primitives) - JS platform: stdlib.math module; strict string->number parsing (rejects partial matches like "fg" in base 16) - OCaml: expt, quotient, gcd, lcm, number->string (radix), string->number (radix); atan updated to accept optional 2nd arg (atan2 form) - spec/tests/test-math.sx: 44 tests — trig/inverse trig, expt, quotient semantics, gcd/lcm, radix formatting/parsing, tower integration - JS: 2311/4801 (+2 net); OCaml: 4547/5629 (+1 net); zero regressions in math area Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1427,6 +1427,49 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
||||
if (a === 0) return 0;
|
||||
return 32 - Math.clz32(Math.abs(a));
|
||||
};
|
||||
''',
|
||||
"stdlib.math": '''
|
||||
// stdlib.math
|
||||
PRIMITIVES["sin"] = Math.sin;
|
||||
PRIMITIVES["cos"] = Math.cos;
|
||||
PRIMITIVES["tan"] = Math.tan;
|
||||
PRIMITIVES["asin"] = Math.asin;
|
||||
PRIMITIVES["acos"] = Math.acos;
|
||||
PRIMITIVES["atan"] = function(y, x) { return arguments.length >= 2 ? Math.atan2(y, x) : Math.atan(y); };
|
||||
PRIMITIVES["exp"] = Math.exp;
|
||||
PRIMITIVES["log"] = Math.log;
|
||||
PRIMITIVES["expt"] = Math.pow;
|
||||
PRIMITIVES["quotient"] = function(a, b) { return Math.trunc(a / b); };
|
||||
PRIMITIVES["gcd"] = function(a, b) {
|
||||
a = Math.abs(a); b = Math.abs(b);
|
||||
while (b) { var t = b; b = a % b; a = t; }
|
||||
return a;
|
||||
};
|
||||
PRIMITIVES["lcm"] = function(a, b) {
|
||||
var g = PRIMITIVES["gcd"](Math.abs(a), Math.abs(b));
|
||||
return g === 0 ? 0 : Math.abs(a / g * b);
|
||||
};
|
||||
PRIMITIVES["number->string"] = function(n, r) {
|
||||
if (r === undefined || r === null) return String(n);
|
||||
return Math.floor(n).toString(r);
|
||||
};
|
||||
PRIMITIVES["string->number"] = function(s, r) {
|
||||
s = String(s);
|
||||
if (r !== undefined && r !== null) {
|
||||
var radix = r | 0;
|
||||
var valid = "0123456789abcdefghijklmnopqrstuvwxyz".slice(0, radix);
|
||||
var norm = s.toLowerCase();
|
||||
var start = norm[0] === '-' ? 1 : 0;
|
||||
if (norm.length <= start) return NIL;
|
||||
for (var i = start; i < norm.length; i++) {
|
||||
if (valid.indexOf(norm[i]) === -1) return NIL;
|
||||
}
|
||||
return parseInt(s, radix);
|
||||
}
|
||||
if (s === '') return NIL;
|
||||
var n = Number(s);
|
||||
return isNaN(n) ? NIL : n;
|
||||
};
|
||||
''',
|
||||
"stdlib.hash-table": '''
|
||||
// stdlib.hash-table
|
||||
|
||||
@@ -210,7 +210,10 @@ let () =
|
||||
register "acos" (fun args ->
|
||||
match args with [a] -> Number (Float.acos (as_number a)) | _ -> raise (Eval_error "acos: 1 arg"));
|
||||
register "atan" (fun args ->
|
||||
match args with [a] -> Number (Float.atan (as_number a)) | _ -> raise (Eval_error "atan: 1 arg"));
|
||||
match args with
|
||||
| [a] -> Number (Float.atan (as_number a))
|
||||
| [y; x] -> Number (Float.atan2 (as_number y) (as_number x))
|
||||
| _ -> raise (Eval_error "atan: 1-2 args"));
|
||||
register "atan2" (fun args ->
|
||||
match args with [a; b] -> Number (Float.atan2 (as_number a) (as_number b))
|
||||
| _ -> raise (Eval_error "atan2: 2 args"));
|
||||
@@ -320,6 +323,85 @@ let () =
|
||||
| [Number n] -> Integer (int_of_float (Float.round n))
|
||||
| [a] -> Integer (int_of_float (Float.round (as_number a)))
|
||||
| _ -> raise (Eval_error "inexact->exact: 1 arg"));
|
||||
register "expt" (fun args ->
|
||||
match args with
|
||||
| [Integer a; Integer b] when b >= 0 ->
|
||||
let rec ipow base e acc = if e = 0 then acc else ipow base (e - 1) (acc * base) in
|
||||
Integer (ipow a b 1)
|
||||
| [a; b] -> Number (Float.pow (as_number a) (as_number b))
|
||||
| _ -> raise (Eval_error "expt: 2 args"));
|
||||
register "quotient" (fun args ->
|
||||
match args with
|
||||
| [Integer a; Integer b] -> Integer (Int.div a b)
|
||||
| [a; b] ->
|
||||
let n = as_number a /. as_number b in
|
||||
Integer (int_of_float (if n >= 0.0 then floor n else ceil n))
|
||||
| _ -> raise (Eval_error "quotient: 2 args"));
|
||||
let rec igcd a b = if b = 0 then a else igcd b (a mod b) in
|
||||
register "gcd" (fun args ->
|
||||
match args with
|
||||
| [Integer a; Integer b] -> Integer (igcd (abs a) (abs b))
|
||||
| [a; b] ->
|
||||
let rec fgcd a b = if b = 0.0 then a else fgcd b (Float.rem a b) in
|
||||
Number (fgcd (abs_float (as_number a)) (abs_float (as_number b)))
|
||||
| _ -> raise (Eval_error "gcd: 2 args"));
|
||||
register "lcm" (fun args ->
|
||||
match args with
|
||||
| [Integer a; Integer b] ->
|
||||
let g = igcd (abs a) (abs b) in
|
||||
if g = 0 then Integer 0 else Integer (abs a / g * abs b)
|
||||
| [a; b] ->
|
||||
let a = abs_float (as_number a) and b = abs_float (as_number b) in
|
||||
let rec fgcd a b = if b = 0.0 then a else fgcd b (Float.rem a b) in
|
||||
let g = fgcd a b in
|
||||
if g = 0.0 then Number 0.0 else Number (a /. g *. b)
|
||||
| _ -> raise (Eval_error "lcm: 2 args"));
|
||||
register "number->string" (fun args ->
|
||||
let digits = "0123456789abcdefghijklmnopqrstuvwxyz" in
|
||||
let int_to_radix n r =
|
||||
if n = 0 then "0"
|
||||
else begin
|
||||
let neg = n < 0 in
|
||||
let buf = Buffer.create 16 in
|
||||
let rec go n = if n > 0 then begin go (n / r); Buffer.add_char buf digits.[n mod r] end in
|
||||
go (abs n);
|
||||
(if neg then "-" else "") ^ Buffer.contents buf
|
||||
end
|
||||
in
|
||||
match args with
|
||||
| [Integer n] -> String (string_of_int n)
|
||||
| [Number f] -> String (Printf.sprintf "%g" f)
|
||||
| [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)
|
||||
| [Number f; Integer r] ->
|
||||
if r < 2 || r > 36 then raise (Eval_error "number->string: radix out of range");
|
||||
String (int_to_radix (int_of_float f) r)
|
||||
| _ -> raise (Eval_error "number->string: 1-2 args"));
|
||||
register "string->number" (fun args ->
|
||||
match args with
|
||||
| [String s] ->
|
||||
(try Integer (int_of_string s)
|
||||
with _ -> try Number (float_of_string s)
|
||||
with _ -> Nil)
|
||||
| [String s; Integer r] ->
|
||||
(try
|
||||
let neg = String.length s > 0 && s.[0] = '-' in
|
||||
let start = if neg then 1 else 0 in
|
||||
let n = ref 0 in
|
||||
for i = start to String.length s - 1 do
|
||||
let c = Char.code s.[i] in
|
||||
let d = if c >= 48 && c <= 57 then c - 48
|
||||
else if c >= 97 && c <= 122 then c - 87
|
||||
else if c >= 65 && c <= 90 then c - 55
|
||||
else raise Exit
|
||||
in
|
||||
if d >= r then raise Exit;
|
||||
n := !n * r + d
|
||||
done;
|
||||
Integer (if neg then - !n else !n)
|
||||
with _ -> Nil)
|
||||
| _ -> raise (Eval_error "string->number: 1-2 args"));
|
||||
register "parse-int" (fun args ->
|
||||
let parse_leading_int s =
|
||||
let len = String.length s in
|
||||
|
||||
Reference in New Issue
Block a user