From 551c24c5a0f012945557864815e8ab78d78f6709 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 10 May 2026 10:17:12 +0000 Subject: [PATCH] js-on-sx: Math.round/max/min spec edges (NaN, +/-Infinity, +/-0) --- lib/js/runtime.sx | 46 +++++++++++++++++++++++++++++++++++++++------- plans/js-on-sx.md | 2 ++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx index 8ecbe495..c441039d 100644 --- a/lib/js/runtime.sx +++ b/lib/js/runtime.sx @@ -3879,45 +3879,77 @@ (define js-math-ceil (fn (x) (ceil (js-to-number x)))) -(define js-math-round (fn (x) (floor (+ (js-to-number x) 0.5)))) +(define + js-math-round + (fn + (x) + (let + ((n (js-to-number x))) + (cond + ((js-number-is-nan n) (js-nan-value)) + ((= n (js-infinity-value)) n) + ((= n (- 0 (js-infinity-value))) n) + ((= n 0) n) + (else (floor (+ n 0.5))))))) (define js-math-max (fn (&rest args) (cond - ((empty? args) -inf) - (else (js-math-max-loop (first args) (rest args)))))) + ((empty? args) (- 0 (js-infinity-value))) + (else (js-math-max-loop (js-to-number (first args)) (rest args)))))) (define js-math-max-loop (fn (acc xs) (cond + ((js-number-is-nan acc) (js-nan-value)) ((empty? xs) acc) (else (let ((h (js-to-number (first xs)))) - (js-math-max-loop (if (> h acc) h acc) (rest xs))))))) + (cond + ((js-number-is-nan h) (js-nan-value)) + ((> h acc) (js-math-max-loop h (rest xs))) + ((and (= h 0) (= acc 0)) + (js-math-max-loop (if (js-is-positive-zero? h) h acc) (rest xs))) + (else (js-math-max-loop acc (rest xs))))))))) (define js-math-min (fn (&rest args) (cond - ((empty? args) inf) - (else (js-math-min-loop (first args) (rest args)))))) + ((empty? args) (js-infinity-value)) + (else (js-math-min-loop (js-to-number (first args)) (rest args)))))) (define js-math-min-loop (fn (acc xs) (cond + ((js-number-is-nan acc) (js-nan-value)) ((empty? xs) acc) (else (let ((h (js-to-number (first xs)))) - (js-math-min-loop (if (< h acc) h acc) (rest xs))))))) + (cond + ((js-number-is-nan h) (js-nan-value)) + ((< h acc) (js-math-min-loop h (rest xs))) + ((and (= h 0) (= acc 0)) + (js-math-min-loop (if (js-is-positive-zero? h) acc h) (rest xs))) + (else (js-math-min-loop acc (rest xs))))))))) + +(define + js-is-positive-zero? + (fn + (n) + (cond + ((not (= n 0)) false) + ((= (type-of n) "rational") true) + (else (= (/ 1.0 (exact->inexact n)) (js-infinity-value)))))) (define js-math-random (fn () 0)) diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md index 71332712..1cbfac95 100644 --- a/plans/js-on-sx.md +++ b/plans/js-on-sx.md @@ -158,6 +158,8 @@ Each item: implement → tests → update progress. Mark `[x]` when tests green. Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. +- 2026-05-10 — **`Math.round` / `Math.max` / `Math.min` honour spec edge cases for NaN, ±Infinity, and ±0.** `Math.round(NaN)` was returning 0 because `floor(NaN+0.5)` doesn't propagate NaN; ditto `±Infinity` paths. `Math.max({})` silently returned `-Infinity` (initial accumulator) because the first arg wasn't ToNumber'd. `Math.max(0, -0)` returned `-0` because `>` doesn't distinguish them. Rewrites: round NaN/±Infinity/±0 short-circuits; max/min ToNumber the first arg, propagate NaN immediately, and use a `js-is-positive-zero?` (rational-safe) tiebreaker so `Math.max(0, -0) === 0` per spec. Result: built-ins/Math/round 5/10 → 8/10 (+3). Math/max 6/9 → 8/9 (+2). Math/min 6/9 → 8/9 (+2). conformance.sh: 148/148. + - 2026-05-10 — **`Map.prototype.*` and `Set.prototype.*` raise TypeError when called on non-Map / non-Set `this`.** All five `js-map-do-*` and four `js-set-do-*` helpers were assuming `this` had `__map_keys__` / `__set_items__`, so `Map.prototype.clear.call({})` silently returned undefined (after creating dangling state) instead of throwing. Added `js-map-check!` / `js-set-check!` guards run as the first step of each method; raise spec-correct `TypeError` instances. Result: built-ins/Map 18/30 → 22/30 (+4). built-ins/Set 15/30 → 28/30 (+13). conformance.sh: 148/148. - 2026-05-10 — **`Date.UTC` / `new Date(...)` propagate NaN/±Infinity arguments and return NaN.** `Date.UTC()` (no args) returned 0 instead of NaN; `Date.UTC(NaN, ...)` did the math and produced bogus ms; `new Date(year, NaN)` constructed a normal Date instead of an invalid one. Added `js-date-args-have-nan?` (also detects ±Infinity and propagates from rationals) used by both `Date.UTC` and the multi-arg constructor branch; UTC now returns NaN on no-arg / any-NaN-arg / out-of-range result, and `new Date(args)` stores NaN in `__date_value__` when any arg is NaN. Also fixed `js-date-from-one(undefined)` to return NaN. Result: built-ins/Date/UTC 6/16 → 10/16 (+4). Date 17/30 → 26/30 (timeouts dropped from 12 → 4 because invalid Dates now short-circuit). conformance.sh: 148/148.