204

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

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

An 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

6
  • 3
    I read 3 times before I understood what you meant. Please add an example, or explain better... Commented Dec 10, 2009 at 6: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 Commented Dec 10, 2009 at 6:14
  • Possible duplicate of How to calculate the angle between a line and the horizontal axis? Commented Jul 9, 2017 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 Commented Jul 9, 2017 at 21:46
  • 1
    if you use Unity c# script, you can use Mathf.DeltaAngle function. Commented Jan 22, 2021 at 7:53

12 Answers 12

285

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 
Sign up to request clarification or add additional context in comments.

11 Comments

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
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.)
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
In C++ you can use std::fmod(a,360), or fmod(a,360) to use floating point modulo.
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
|
188

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.

8 Comments

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].
a one line simple solution and solved for me(not the selected answer ;) ). but tan inverse is a costly process.
For me, the most elegant solution. Shame it might be computationally expensive.
Unfortunately, this also isn't as precise as the other solutions.
|
61

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.

6 Comments

@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.
The second version also doesn't work for me. Try 350 and 0 for example. It should return -10 but returns -350
@kjyv I can't reproduce the behavior you describe. Can you post the exact code?
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).
Note that, as of Python 3, you can actually use tau natively! Just write from math import tau.
|
15

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); } 

See it working here: https://www.desmos.com/calculator/sbgxyfchjr

For signed angle: return fmod(fabs(x - y) + c, 2*c) - c;

In some other programming languages where mod of negative numbers are positive, the inner abs can be eliminated.

1 Comment

Great idea, but it doesn't yield signed angle.
11

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) 

5 Comments

Ah... the answer is a Python function by the way. Sorry, I was in Python mode for a moment. Hope that's okay.
I shall plug the new formula into my code upstairs and see what becomes of it! ( thankyou ^_^ )
I'm pretty sure PeterB's answer is correct too. And evilly hackish. :)
But this one contains no trig functions :)
might be a good idea to import math at the start of the file instead
6

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

1 Comment

Has no signed output tho
5

Arithmetical (as opposed to algorithmic) solution:

angle = Pi - abs(abs(a1 - a2) - Pi); 

2 Comments

Fails if abs(a1-a2) >>> 360 . Use this instead: stackoverflow.com/a/52432897/6050364
2

I absolutely love Peter B's answer above, but if you need a dead simple approach that produces the same results, here it is:

function absAngle(a) { // this yields correct counter-clock-wise numbers, like 350deg for -370 return (360 + (a % 360)) % 360; } function angleDelta(a, b) { // https://gamedev.stackexchange.com/a/4472 let delta = Math.abs(absAngle(a) - absAngle(b)); let sign = absAngle(a) > absAngle(b) || delta >= 180 ? -1 : 1; return (180 - Math.abs(delta - 180)) * sign; } // sample output for (let angle = -370; angle <= 370; angle+=20) { let testAngle = 10; console.log(testAngle, "->", angle, "=", angleDelta(testAngle, angle)); }

One thing to note is that I deliberately flipped the sign: counter-clockwise deltas are negative, and clockwise ones are positive

Comments

0

Old ass thread but ... I had the same problem in Desmos. Here was my implementation if useful to anyone:

Screenshot of the LaTex: https://i.sstatic.net/6RzDA.png

Parameters v1, and v2 are vectors. Plug in like points. Such as: v1 = (0,0), v2 = (0,1).

I was thinking there might be some way to optimize it with complex number multiplication but I'm just gonna leave it there bc I'm just using Desmos to throw together a model.

Comments

0

The Lua code to compare two vectors, given with :

function angleComparing(x1, y1, x2, y2) local a1 = math.atan2(y1, x1) local a2 = math.atan2(y2, x2) local diff = (a2 - a1 + math.pi)%(2*math.pi) - math.pi return diff -- returns values in range [-pi, pi] end 

Usage:

for i = 360, -360, -180 do for j = -120, 120, 120 do local a1 = math.rad (i) local a2 = math.rad (j) local dy1, dx1 = math.sin (a1), math.cos (a1) local dy2, dx2 = math.sin (a2), math.cos (a2) local dif = angleComparing(dx1, dy1, dx2, dy2) print (i, j, string.format('%.1f', math.deg (dif))) end end 

Result:

(angle2 + difference == angle1)

angle1, angle2, difference 360 -120 120.0 360 0 0.0 360 120 -120.0 180 -120 -60.0 180 0 -180.0 180 120 60.0 0 -120 120.0 0 0 0.0 0 120 -120.0 -180 -120 -60.0 -180 0 -180.0 -180 120 60.0 -360 -120 120.0 -360 0 0.0 -360 120 -120.0 

Comments

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

1 Comment

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.
-1

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

double deltaOrientation = angle1 - angle2; double delta = remainder(deltaOrientation, 2*M_PI); 

1 Comment

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.