Skip to content

Commit 162c5bf

Browse files
authored
Merge pull request #1866 from brownplt/jsnums-test-atan2-quadrants
Add jsnums-test spec for atan2 quadrant correctness
2 parents 807b067 + 7286b0d commit 162c5bf

1 file changed

Lines changed: 46 additions & 0 deletions

File tree

tests/jsnums-test/jsnums-test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,52 @@ R(["pyret-base/js/js-numbers"], function(JNlib) {
4141
expect(function() { JN.makeBignum(+1.5).acos(); }).toThrow('domainError');
4242

4343
});
44+
45+
it("atan2 four quadrants and boundaries", function() {
46+
// atan2(y, x) takes y first, x second (math convention).
47+
// The x<0 and (x>0, y<0) branches historically leaked bare
48+
// Math.PI / 2*Math.PI through `add`, because JS-number+JS-number
49+
// short-circuits and returns its raw sum.
50+
51+
// x > 0, y = 0: must be exact integer 0, NOT Roughnum 0.0.
52+
// Multiples of π are intrinsically irrational, but this case is
53+
// representable exactly and "atan2(0, 5) == 0" is the kind of
54+
// thing math teachers expect to be unambiguous. Implementation
55+
// routes through atan(divide(0, x)) -> atan(0) -> integer 0.
56+
expect(JN.isInteger(JN.atan2(0, 1))).toBe(true);
57+
expect(JN.equals(JN.atan2(0, 1), 0)).toBe(true);
58+
expect(JN.isInteger(JN.atan2(0, 5))).toBe(true);
59+
expect(JN.equals(JN.atan2(0, 5), 0)).toBe(true);
60+
expect(JN.isInteger(JN.atan2(0, JN.makeBignum(5)))).toBe(true);
61+
expect(JN.equals(JN.atan2(0, JN.makeBignum(5)), 0)).toBe(true);
62+
63+
// x > 0, y > 0 (1st quadrant interior): atan(y/x), irrational.
64+
expect(JN.isRoughnum(JN.atan2(1, 1))).toBe(true);
65+
66+
// x = 0, y > 0: π/2 (Roughnum)
67+
expect(JN.isRoughnum(JN.atan2(1, 0))).toBe(true);
68+
expect(JN.toFixnum(JN.atan2(1, 0))).toEqual(Math.PI / 2);
69+
70+
// x < 0, y >= 0 (2nd quadrant): atan(y/x) + π
71+
expect(JN.isRoughnum(JN.atan2(1, -1))).toBe(true);
72+
expect(JN.isRoughnum(JN.atan2(0, -1))).toBe(true);
73+
// atan2(0, -1) = π exactly. This case used to leak Math.PI as a raw JS double.
74+
expect(JN.toFixnum(JN.atan2(0, -1))).toEqual(Math.PI);
75+
76+
// x < 0, y < 0 (3rd quadrant): atan(y/x) + π
77+
expect(JN.isRoughnum(JN.atan2(-1, -1))).toBe(true);
78+
79+
// x = 0, y < 0: 3π/2. Note: jsnums returns angles in [0, 2π) here,
80+
// unlike JS Math.atan2 which would give -π/2 for this case.
81+
expect(JN.isRoughnum(JN.atan2(-1, 0))).toBe(true);
82+
expect(JN.toFixnum(JN.atan2(-1, 0))).toEqual(3 * Math.PI / 2);
83+
84+
// x > 0, y < 0 (4th quadrant): atan(y/x) + 2π. Used to leak 2*Math.PI.
85+
expect(JN.isRoughnum(JN.atan2(-1, 1))).toBe(true);
86+
87+
// (0, 0) is out of domain.
88+
expect(function() { JN.atan2(0, 0); }).toThrow('domainError');
89+
});
4490
});
4591

4692
jazz.execute();

0 commit comments

Comments
 (0)