The idea

Forward trig: angle → ratio. Inverse trig: ratio → angle.

$$ \theta = \arcsin x \iff \sin\theta = x, \qquad \theta = \arccos x \iff \cos\theta = x, \qquad \theta = \arctan x \iff \tan\theta = x $$

Also written \(\sin^{-1}, \cos^{-1}, \tan^{-1}\) — the \(-1\) means inverse function, not reciprocal (\(\sin^{-1}x \ne 1/\sin x = \csc x\)).

Restricted ranges (principal values)

Since \(\sin\) repeats, inverses return one canonical angle:

Function Input domain Output range
\(\arcsin x\) \([−1, 1]\) \([−90°, 90°]\) (\([−\pi/2, \pi/2]\))
\(\arccos x\) \([−1, 1]\) \([0°, 180°]\) (\([0, \pi]\))
\(\arctan x\) all real \((−90°, 90°)\) (\((−\pi/2, \pi/2)\))

There are always infinitely many coterminal solutions; code returns the principal one. Add \(360°k\) (or use symmetry) for others.

import math
print(math.degrees(math.asin(0.5)))   # 30.0
print(math.degrees(math.acos(0.5)))   # 60.0
print(math.degrees(math.atan(1.0)))   # 45.0

The atan2 upgrade (read this!)

atan(y/x) loses quadrant information: \((x, y) = (1, 1)\) and \((−1, −1)\) both give ratio 1. atan2(y, x) takes the signs separately and returns the correct angle in \((−\pi, \pi]\).

$$ \text{atan2}(y, x) = \text{angle of point } (x, y) \text{ from the +x axis} $$
import math
print(math.degrees(math.atan(1)))              # 45.0 — fine here
print(math.degrees(math.atan2(1, 1)))          # 45.0
print(math.degrees(math.atan2(-1, -1)))        # -135.0 (correct quadrant III)
# math.atan(-1/-1) would wrongly say 45 degrees!
console.log(Math.atan2(1, 1) * 180 / Math.PI);    // 45
console.log(Math.atan2(-1, -1) * 180 / Math.PI);  // -135 (correct)
console.log(Math.atan2(0, -1) * 180 / Math.PI);   // 180

Rules of thumb:

  • Angle of a vector / direction to a target: always atan2(dy, dx). Never atan(dy/dx).
  • Convert \((−180°, 180°]\) output to \([0°, 360°)\) with % 360 if you need compass-style angles.
  • Argument order is (y, x) — the most common bug is swapping them.
// Aim from (x1, y1) at (x2, y2), screen coords (y down):
function angleTo(x1, y1, x2, y2) {
  return Math.atan2(y2 - y1, x2 - x1); // radians
}

Common patterns

import math
# Angle from adjacent/hypotenuse etc. — pick the matching inverse:
theta = math.asin(4 / 5)          # opposite/hypotenuse
theta = math.acos(3 / 5)          # adjacent/hypotenuse
theta = math.atan(4 / 3)          # opposite/adjacent (QI/QIV only)
theta = math.atan2(4, 3)          # preferred: full-circle safe

# Clamp before asin/acos — floats like 1.0000000002 must not crash:
def safe_acos(x): return math.acos(max(-1.0, min(1.0, x)))
def safe_asin(x): return math.asin(max(-1.0, min(1.0, x)))

Practice

  1. Compute \(\arcsin(1)\), \(\arccos(0)\), \(\arctan(√3)\) by hand (degrees), then verify in code.
  2. Explain why asin(2) raises / returns NaN. What does the domain restriction mean geometrically?
  3. Points A(0,0), B(−3, 3). Compute the direction with atan2 and with atan. Which is right, and why?
  4. Write compass_bearing(dx, dy) returning degrees in \([0, 360)\) measured clockwise from north (hint: atan2(dx, dy), not (dy, dx)).

Next: Trigonometry in Code — putting it all together in real programs.