Skip to content

Add jsnums-test spec for atan2 quadrant correctness#1866

Merged
jpolitz merged 2 commits into
horizonfrom
jsnums-test-atan2-quadrants
May 5, 2026
Merged

Add jsnums-test spec for atan2 quadrant correctness#1866
jpolitz merged 2 commits into
horizonfrom
jsnums-test-atan2-quadrants

Conversation

@jpolitz

@jpolitz jpolitz commented May 4, 2026

Copy link
Copy Markdown
Member

Joe says:

Claude helped write some tests for the cases of atan2 that are likely to short-circuit and produce some of the fixnum errors/roughnum-vs-exact-int errors that check-pyret-number was designed for.

(Probably didn't need Claude for this TBH)

Builds on #1864, this can retarget onto horizon once that's merged

Claude says:

Adds an it("atan2 four quadrants and boundaries") block that pins down atan2's return-type contract across every combination of axis sign and quadrant. atan2's x<0 and (x>0, y<0) branches historically leaked bare Math.PI / 2*Math.PI through add, because JS-number+JS-number takes a fast path that returns the raw sum unchanged — so atan2(0, -1) returned the JS double 3.141592653589793 rather than a Roughnum, with branch- dependent type tags visible to user code. The library fix landed recently; this spec captures the expected behavior so future regressions trip CI rather than reappearing as branch-by-branch mysteries.

Uses isPyretNumber rather than isRoughnum as the predicate because atan(0) currently short-circuits to an integer fixnum (not a Roughnum), so cases like atan2(0, 1) come back as 0 rather than ~0. That's a separate trig-type-consistency wart and is called out in a comment.

Pins atan2(-1, 0) at 3π/2 explicitly: jsnums uses [0, 2π) for the negative-y axis, unlike JS Math.atan2 which would give -π/2 here. If we ever decide to switch conventions, this assertion makes the choice explicit instead of silently regressing user code.

Joe says:

Claude helped write some tests for the cases of atan2 that are likely to
short-circuit and produce some of the fixnum errors/roughnum-vs-exact-int
errors that check-pyret-number was designed for.

Builds on #1864, this can rebsae onto horizon once that's merged

Claude says:

Adds an `it("atan2 four quadrants and boundaries")` block that pins down
atan2's return-type contract across every combination of axis sign and
quadrant. atan2's x<0 and (x>0, y<0) branches historically leaked bare
Math.PI / 2*Math.PI through `add`, because JS-number+JS-number takes a
fast path that returns the raw sum unchanged — so atan2(0, -1) returned
the JS double 3.141592653589793 rather than a Roughnum, with branch-
dependent type tags visible to user code. The library fix landed
recently; this spec captures the expected behavior so future
regressions trip CI rather than reappearing as branch-by-branch
mysteries.

Uses `isPyretNumber` rather than `isRoughnum` as the predicate because
atan(0) currently short-circuits to an integer fixnum (not a Roughnum),
so cases like atan2(0, 1) come back as 0 rather than ~0. That's a
separate trig-type-consistency wart and is called out in a comment.

Pins atan2(-1, 0) at 3π/2 explicitly: jsnums uses [0, 2π) for the
negative-y axis, unlike JS Math.atan2 which would give -π/2 here.
If we ever decide to switch conventions, this assertion makes the
choice explicit instead of silently regressing user code.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@blerner

blerner commented May 4, 2026

Copy link
Copy Markdown
Member

To be clear, I'm pretty sure we wanted atan(0) to be exact integer zero, so that math teachers can't claim that "we can't even get that simple case exactly right" or some argument like that...

Base automatically changed from check-pyret-number to horizon May 5, 2026 16:19
Joe says:

Leaving in both isInteger and the toBe(0) because of what Claude said, but also
because this test is coming right after a regression/update around isInteger
and isPyretNumber, so it's useful to check that

Claude says:

- atan2(0, x>0) now asserts `isInteger` + `equals(_, 0)`, covering
  small int, larger int, and bignum x. `equals`/`isInteger` (rather
  than `===`/`toBe(0)`) keeps the spec at the "exact integer with
  value 0" level instead of pinning the JS-number representation.
- Every other case is provably irrational (multiples of π) and now
  asserts `isRoughnum` directly.

`make jsnums-test` passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@blerner blerner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm

@jpolitz jpolitz merged commit 162c5bf into horizon May 5, 2026
2 checks passed
@jpolitz jpolitz deleted the jsnums-test-atan2-quadrants branch June 25, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants