#300 Aug 5, 2026

300. atan2 — The Angle Formula That Knows Its Quadrant

(dy / dx).atan() happily returns an angle — in the wrong quadrant half the time, and NaN at the origin. atan2 takes both components and gets every case right.

This morning’s bite 299 covered hypot, the robust way to get the length of a vector. Its companion problem is the angle — and the naive formula fails in a sneakier way.

atan alone can only return angles between −π/2 and π/2, because the division throws away the signs of the inputs:

1
2
3
4
5
let (dx, dy) = (-3.0_f64, 4.0);

// (-3, 4) is up-and-left: second quadrant
let naive = (dy / dx).atan();
assert!(naive < 0.0); // ...a negative angle?!

4.0 / -3.0 is the same number as -4.0 / 3.0, so atan can’t tell the second quadrant from the fourth. atan2 takes dy and dx separately, keeps both signs, and returns the full −π to π range:

1
2
3
4
5
6
let angle = dy.atan2(dx);
assert!((angle - 2.2142974355881810).abs() < 1e-12);

// naive answer is off by exactly π
let err = angle - naive;
assert!((err - std::f64::consts::PI).abs() < 1e-12);

It also survives the edge the division dies on — the origin:

1
2
assert!((0.0_f64 / 0.0).atan().is_nan());
assert_eq!(0.0_f64.atan2(0.0), 0.0);

Together with hypot, this is the complete cartesian-to-polar kit, and the round trip closes to within an ulp:

1
2
3
4
let r = dx.hypot(dy);
let theta = dy.atan2(dx);
assert!((r * theta.cos() - dx).abs() < 1e-12);
assert!((r * theta.sin() - dy).abs() < 1e-12);

Argument order trips everyone up once: it’s y.atan2(x) — the vertical component is self. If your angles keep coming out mirrored across the diagonal, you’ve swapped them.

← Previous 299. hypot — The Distance Formula That Doesn't Overflow Next → 301. ln_1p / exp_m1 — Adding 1.0 Destroys Your Tiny Float Before ln Ever Runs