@@ -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