152

Given 2 angles in the range -PI -> PI around a coordinate, what is the value of the smallest of the 2 angles between them?

Taking into account that the difference between PI and -PI is not 2 PI but zero.

Example:

Imagine a circle, with 2 lines coming out from the center, there are 2 angles between those lines, the angle they make on the inside aka the smaller angle, and the angle they make on the outside, aka the bigger angle. Both angles when added up make a full circle. Given that each angle can fit within a certain range, what is the smaller angles value, taking into account the rollover

Pikamander2
  • 4,767
  • 3
  • 37
  • 53
Tom J Nowell
  • 8,415
  • 16
  • 57
  • 88
  • 2
    I read 3 times before I understood what you meant. Please add an example, or explain better... – Kobi Dec 10 '09 at 06:12
  • Imagine a circle, with 2 lines comign out from the center, there are 2 angles between those lines, the angle they make on the inside aka the smaller angle, and the angle they make on the outside, aka the bigger angle. Both angles when added up make a full circle. Given that each angle can fit within a certain range, what is the smaller angles value, taking into account the rollover – Tom J Nowell Dec 10 '09 at 06:14
  • Possible duplicate of [How to calculate the angle between a line and the horizontal axis?](https://stackoverflow.com/questions/7586063/how-to-calculate-the-angle-between-a-line-and-the-horizontal-axis) – Jim G. Jul 09 '17 at 20:33
  • 2
    @JimG. this isn't the same question, in this question the angle P1 used in the other question would be the incorrect answer, it would be the other, smaller angle. Also, there is no guarantee that the angle is with the horizontal axis – Tom J Nowell Jul 09 '17 at 21:46
  • if you use Unity c# script, you can use Mathf.DeltaAngle function. – bigant02 Jan 22 '21 at 07:53

9 Answers9

215

This gives a signed angle for any angles:

a = targetA - sourceA
a = (a + 180) % 360 - 180

Beware in many languages the modulo operation returns a value with the same sign as the dividend (like C, C++, C#, JavaScript, full list here). This requires a custom mod function like so:

mod = (a, n) -> a - floor(a/n) * n

Or so:

mod = (a, n) -> (a % n + n) % n

If angles are within [-180, 180] this also works:

a = targetA - sourceA
a += (a>180) ? -360 : (a<-180) ? 360 : 0

In a more verbose way:

a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
bennedich
  • 11,188
  • 6
  • 30
  • 40
  • Simpler and makes more sense read out loud, though effectively the same thing, first bti figures out the angle, second part makes sure its always the smaller of the 2 possible angles – Tom J Nowell Oct 25 '11 at 11:48
  • 2
    although one might want to do a % 360, e.g. if I had the angle 0 and the target angle 721, the correct answer would be 1, the answer given by the above would be 361 – Tom J Nowell Oct 25 '11 at 11:51
  • Yes that's true and deliberate but definitely worth pointing out. In my example I previously got `targetA` and `sourceA` from `atan2`, hence their absolute angles are never greater than 360. – bennedich Oct 26 '11 at 01:31
  • the latter approach also works for other clamped angle intervals such as [0, 360) as the two angles are subtracted. – vidstige Aug 29 '14 at 14:02
  • I made a js gist for this [here](https://gist.github.com/Aaronduino/4068b058f8dbc34b4d3a9eedc8b2cbe0. – user31415 Jun 25 '16 at 04:11
  • 1
    A more concise, though potentially more expensive, equivalent of the latter approach's second statement, is `a -= 360*sgn(a)*(abs(a) > 180)`. (Come to think of it, if you've branchless implementations of `sgn` and `abs`, then that characteristic might actually start to compensate for needing two multiplications.) – mmirate Jul 25 '16 at 19:51
  • 2
    The "Signed angle for any angle" example seems to work in most scenarios, with one exception. In scenario `double targetA = 2; double sourceA = 359;` 'a' will be equal to -357.0 instead of 3.0 – Stevoisiak Apr 26 '17 at 21:49
  • 3
    In C++ you can use std::fmod(a,360), or fmod(a,360) to use floating point modulo. – Joeppie Mar 23 '18 at 12:27
  • Joeppie: That's true. But will it behave as mentioned for the sign of the dividend? – stephanmg Apr 15 '19 at 21:16
  • Mh. This a += (a>180) ? -360 : (a – stephanmg Apr 18 '19 at 15:04
  • If the `%` operator acts like remainder in your language (retains sign), you can simply add an extra 360 instead of defining a modulus function: `a = (a + 540) % 360 - 180` As stated above, this only works for angles within 360 of each other, which may often be the case. Otherwise: `a = ((a % 360) + 540) % 360 - 180` – RedMatt May 20 '21 at 16:05
152

x is the target angle. y is the source or starting angle:

atan2(sin(x-y), cos(x-y))

It returns the signed delta angle. Note that depending on your API the order of the parameters for the atan2() function might be different.

Peter B
  • 22,874
  • 4
  • 20
  • 18
  • 15
    `x-y` gives you the difference in angle, but it may be out of the desired bounds. Think of this angle defining a point on the unit circle. The coordinates of that point are `(cos(x-y), sin(x-y))`. `atan2` returns the angle for that point (which is equivalent to `x-y`) except its range is [-PI, PI]. – Max Sep 02 '13 at 16:17
  • 3
    This passes the test suite https://gist.github.com/bradphelan/7fe21ad8ebfcb43696b8 – bradgonesurfing Jul 13 '15 at 08:43
  • 3
    a one line simple solution and solved for me(not the selected answer ;) ). but tan inverse is a costly process. – Mohan Kumar Jun 20 '16 at 16:32
  • 2
    For me, the most elegant solution. Shame it might be computationally expensive. – focs Jul 04 '16 at 08:51
  • For me the most elegant solution as well! Solved my problem perfectly (wanted to have a formula that gives me the _signed_ turn angle which is the smaller one from the two possible turn directions/angles). – Jürgen Brauer May 04 '17 at 07:46
  • Unfortunately, this also isn't as precise as the other solutions. – 12Me21 Aug 16 '18 at 18:17
  • I still use this function every time because it's easy to remember, elegant, and avoids any constants. For me that's worth the loss in precision and speed. – mklingen Jun 04 '19 at 14:07
  • This solution works, but is f@%& slow!!! All three trigonometric operations are slow to compute. – Adriel Jr Nov 21 '19 at 16:24
46

If your two angles are x and y, then one of the angles between them is abs(x - y). The other angle is (2 * PI) - abs(x - y). So the value of the smallest of the 2 angles is:

min((2 * PI) - abs(x - y), abs(x - y))

This gives you the absolute value of the angle, and it assumes the inputs are normalized (ie: within the range [0, 2π)).

If you want to preserve the sign (ie: direction) of the angle and also accept angles outside the range [0, 2π) you can generalize the above. Here's Python code for the generalized version:

PI = math.pi
TAU = 2*PI
def smallestSignedAngleBetween(x, y):
    a = (x - y) % TAU
    b = (y - x) % TAU
    return -a if a < b else b

Note that the % operator does not behave the same in all languages, particularly when negative values are involved, so if porting some sign adjustments may be necessary.

Laurence Gonsalves
  • 125,464
  • 31
  • 220
  • 273
  • 1
    @bradgonesurfing That is/was true, but to be fair your tests checked for things that weren't specified in the original question, specifically non-normalized inputs and sign-preservation. The second version in the edited answer should pass your tests. – Laurence Gonsalves Jul 23 '15 at 19:20
  • The second version also doesn't work for me. Try 350 and 0 for example. It should return -10 but returns -350 – kjyv Oct 26 '19 at 00:06
  • @kjyv I can't reproduce the behavior you describe. Can you post the exact code? – Laurence Gonsalves Nov 11 '19 at 23:51
  • Ah, I'm sorry. I've tested exactly your version with rad and degrees in python again and it worked fine. So must have been a mistake in my translation to C# (don't have it anymore). – kjyv Nov 13 '19 at 13:11
  • 2
    Note that, as of Python 3, you can actually use tau natively! Just write `from math import tau`. – mhartl Jan 08 '20 at 19:15
  • Assuming (x,y) = (targetA, source A). Does it work for angles from [-180,180] ? Tried following (-2,-4), (172,-4), (-4, 172), (-172,-170) got answers with sign flipped. Or does it assumes the otherway arround? (x,y) = (sourceA, targetA) ? – AshlinJP Feb 05 '20 at 22:27
8

I rise to the challenge of providing the signed answer:

def f(x,y):
  import math
  return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
David Jones
  • 3,640
  • 2
  • 28
  • 42
7

An efficient code in C++ that works for any angle and in both: radians and degrees is:

inline double getAbsoluteDiff2Angles(const double x, const double y, const double c)
{
    // c can be PI (for radians) or 180.0 (for degrees);
    return c - fabs(fmod(fabs(x - y), 2*c) - c);
}
Adriel Jr
  • 1,357
  • 12
  • 20
6

For UnityEngine users, the easy way is just to use Mathf.DeltaAngle.

Josh
  • 81
  • 1
  • 4
5

Arithmetical (as opposed to algorithmic) solution:

angle = Pi - abs(abs(a1 - a2) - Pi);
Rudolf Meijering
  • 1,509
  • 1
  • 14
  • 20
0

A simple method, which I use in C++ is:

double deltaOrientation = angle1 - angle2;
double delta =  remainder(deltaOrientation, 2*M_PI);
Eduard
  • 1
  • This is wrong, I'm afraid. Consider if angle1 = 0 and angle2 = pi+c, for some c>0. The correct answer should be -(pi-c), but your answer gives pi+c. Bear in mind that the OP explicitly asked for the smaller angle, and the smaller angle should always be less than or equal to pi. – sircolinton Nov 12 '20 at 21:40
-1

There is no need to compute trigonometric functions. The simple code in C language is:

#include <math.h>
#define PIV2 M_PI+M_PI
#define C360 360.0000000000000000000
double difangrad(double x, double y)
{
double arg;

arg = fmod(y-x, PIV2);
if (arg < 0 )  arg  = arg + PIV2;
if (arg > M_PI) arg  = arg - PIV2;

return (-arg);
}
double difangdeg(double x, double y)
{
double arg;
arg = fmod(y-x, C360);
if (arg < 0 )  arg  = arg + C360;
if (arg > 180) arg  = arg - C360;
return (-arg);
}

let dif = a - b , in radians

dif = difangrad(a,b);

let dif = a - b , in degrees

dif = difangdeg(a,b);

difangdeg(180.000000 , -180.000000) = 0.000000
difangdeg(-180.000000 , 180.000000) = -0.000000
difangdeg(359.000000 , 1.000000) = -2.000000
difangdeg(1.000000 , 359.000000) = 2.000000

No sin, no cos, no tan,.... only geometry!!!!

Uli Gue
  • 31
  • 1
  • 8
    Bug! Since you #define PIV2 as "M_PI+M_PI", not "(M_PI+M_PI)", the line `arg = arg - PIV2;` expands to `arg = arg - M_PI + M_PI`, and so does nothing. – canton7 Jan 19 '14 at 11:53