Mathematics7 min readExamples run on PHP 8.5.10

atan2()

Learn how PHP 8.4 atan2() returns the angle of a point in radians, why it beats atan(y / x), and which argument-order mistakes to avoid.

Signature

atan2(float $y, float $x): float

Purpose

atan2() answers a geometry question: given a point at horizontal distance x and vertical distance y from the origin, what is the angle of the line from the origin to that point? The answer comes back in radians, measured counterclockwise from the positive x-axis.

It is similar to atan($y / $x), but it looks at the signs of both arguments separately. That lets it tell apart points that would give the same ratio but lie in different quadrants. It also avoids the division, so x = 0 is not a problem.

Signature

Parameters and defaults

  • $y is the dividend, the vertical component. It comes first.
  • $x is the divisor, the horizontal component. It comes second.

Neither parameter has a default, so both are required. Integers are accepted and converted to floats. Numeric strings are also accepted when strict types are off.

Return value and failure cases

The function returns a float in radians between -M_PI and M_PI inclusive. It does not return false and does not emit warnings for ordinary numbers.

Calling it with the wrong number of arguments, or with a value that cannot be treated as a number (such as an array or the string "abc"), throws a TypeError.

Example 1: one angle per quadrant

The four points below all have a ratio y / x of either 1 or -1, yet atan2() returns four different angles. Each result is rounded to four decimals so the output stays stable.

Example
<?php
$points = [[1, 1], [1, -1], [-1, -1], [-1, 1]];
$angles = [];
foreach ($points as [$y, $x]) {
    $angles[] = round(atan2($y, $x), 4);
}
echo json_encode($angles);
Output
[0.7854,2.3562,-2.3562,-0.7854]

The values are roughly π/4, 3π/4, -3π/4 and -π/4. Positive results are above the x-axis, and negative results are below it.

Example 2: why not atan(y / x)?

The point x = -1, y = 1 is in the upper-left quadrant. Dividing first gives -1, which erases the information that x was negative. atan() can only return values between -π/2 and π/2, so it reports a point in the lower-right direction instead.

Example
<?php
$y = 1;
$x = -1;
echo json_encode([
    'atan' => round(atan($y / $x), 4),
    'atan2' => round(atan2($y, $x), 4),
]);
Output
{"atan":-0.7854,"atan2":2.3562}

The atan2() result, about 2.3562 radians, is the correct 135° direction.

Example 3: a compass-style heading in degrees

A common task is turning an offset into a whole-degree heading from 0 to 359. Convert the radians with rad2deg(), then shift negative angles into the 0–360 range.

Example
<?php
function headingDegrees(int $dx, int $dy): int
{
    $degrees = rad2deg(atan2($dy, $dx));
    return (int) round(fmod($degrees + 360, 360));
}

echo json_encode([
    headingDegrees(3, 4),
    headingDegrees(-3, 4),
    headingDegrees(0, -2),
]);
Output
[53,127,270]

The point straight down gives -90° from atan2(), which the helper turns into 270°.

Edge cases

  • Zero x: atan2(1, 0) is π/2 and atan2(-1, 0) is -π/2. Using atan($y / $x) here would divide by zero and throw DivisionByZeroError.
  • Negative x on the axis: a point on the negative x-axis has an angle of π, and the sign of a zero y can matter. atan2(0.0, -1.0) gives π, while atan2(-0.0, -1.0) gives -π because negative zero is distinct for floats.
  • The origin: atan2(0, 0) has no meaningful direction. Check for it yourself when your program needs to treat that case specially.
  • Range limits: the result can be exactly M_PI or -M_PI, but never beyond them. Remember this if you compare angles or wrap them into another range.
  • Rounding after normalizing: if you round a normalized angle to whole degrees, a value such as 359.7 becomes 360. Use % 360 after rounding if you need a strict 0–359 range.
  • Float precision: radians are floating-point numbers, so compare them with a tolerance or round them before display or tests.

Common mistakes

  1. Swapping the arguments. The order is atan2($y, $x), not ($x, $y). Swapping them reflects the angle across the diagonal, giving π/2 minus the right answer for points in the first quadrant.
  2. Passing a precomputed ratio. Writing atan2($y / $x, 1) brings back the quadrant problem and the division-by-zero risk.
  3. Forgetting units. The result is in radians. Use rad2deg() for degrees, and deg2rad() before feeding angles to sin() or cos().
  4. Expecting 0–2π. The range is -π to π. Negative angles are normal and need adjusting only if your display requires a full turn.
  5. Using screen coordinates without thinking. In many screen systems the y-axis grows downward, so the visual rotation is flipped. Negate $y if you need a conventional mathematical direction.
  6. Comparing floats with ===. Values like atan2(1, 1) * 4 should be compared with a small tolerance, such as abs($a - M_PI) < 1e-12.
  • atan() returns the arc tangent of a single value, limited to -π/2 through π/2.
  • tan() is the tangent function that these two invert.
  • rad2deg() and deg2rad() convert between radians and degrees.
  • hypot() computes the distance from the origin and pairs naturally with atan2() when converting Cartesian coordinates to polar form.
  • pi() and M_PI give the constant for comparing and scaling angles.

Recap

Use atan2($y, $x) whenever you need the direction of a point or vector. It takes the vertical component first, uses both signs to choose the correct quadrant, handles x = 0 without error, and returns a float in radians from -π to π. Convert to degrees only at the edge of your program, and normalize ranges explicitly when you need 0–360.

Tested examples

One angle per quadrant
<?php
$points = [[1, 1], [1, -1], [-1, -1], [-1, 1]];
$angles = [];
foreach ($points as [$y, $x]) {
    $angles[] = round(atan2($y, $x), 4);
}
echo json_encode($angles);
Output
[0.7854,2.3562,-2.3562,-0.7854]
atan2() compared with atan(y / x)
<?php
$y = 1;
$x = -1;
echo json_encode([
    'atan' => round(atan($y / $x), 4),
    'atan2' => round(atan2($y, $x), 4),
]);
Output
{"atan":-0.7854,"atan2":2.3562}
Compass-style heading in whole degrees
<?php
function headingDegrees(int $dx, int $dy): int
{
    $degrees = rad2deg(atan2($dy, $dx));
    return (int) round(fmod($degrees + 360, 360));
}

echo json_encode([
    headingDegrees(3, 4),
    headingDegrees(-3, 4),
    headingDegrees(0, -2),
]);
Output
[53,127,270]