12
votes

I'm looking for a formula to find the shortest distance in degrees between two degree marks on a circle: for instance, 30 degrees and 170 degrees (140 degrees).

The two degree marks can be virtually any real number, and isn't necessarily between 0 and 360 (can be negative, or much greater than 360, for instance -528.2 and 740 (which is 171.8 degrees)). However, the distance should always be <= 180 degrees and >= 0 degrees.

It sounds simple enough. But, I've been trying to find a good solution for this and I've tried a lot of different code but nothing I've found so far works in all the cases I've tried. I'm working in c++. Does anyone have any ideas?

9
abs(deg1 - deg2) will give you the difference, and then some simple modulo math to make it be < 180 at all times should do the trick.Marc B
It sounds like you take the absolute value of both numbers, and subtract the smallest from the largest, based on your examples.Robert Harvey
@RobertHarvey: that actually doesn't work. Imagine the starting point being -90. Covering 4 points you have -90 (aka 270), 0, 90 and 180. If you have a point at -90 and another at 135 then the minimum distance between them is actually 135.NotMe
@ChrisLively: Yes, I realized you need a modulus calculation.Robert Harvey

9 Answers

21
votes
  • Step 1: Get the "raw" difference. For example, given -528.2 and 740.0, this is 1268.2.

    • one way: raw_diff = first > second ? first - second : second - first
    • another way: raw_diff = std::fabs(first - second)
  • Step 2: Subtract a multiple of 360.0 to get a value between 0.0 (inclusive) and 360.0 (exclusive).

    • mod_diff = std::fmod(raw_diff, 360.0)
  • Step 3: If this value is greater than 180.0, subtract it from 360.0.

    • one way: dist = mod_diff > 180.0 ? 360.0 - mod_diff : mod_diff
    • another way: dist = 180.0 - std::fabs(mod_diff - 180.0)

It's probably most readable as a series of statements:

double raw_diff = first > second ? first - second : second - first;
double mod_diff = std::fmod(raw_diff, 360.0);
double dist = mod_diff > 180.0 ? 360.0 - mod_diff : mod_diff;

But if desired, it's not hard to put it all into a single expression:

180.0 - std::fabs(std::fmod(std::fabs(first - second), 360.0) - 180.0)
2
votes

You could also use vector math and trigonometry; angles would be in radians here.

float angle(float angle1, float angle2)
{
  float x1=cos(angle1);
  float y1=sin(angle1);
  float x2=cos(angle2);
  float y2=sin(angle2);

  float dot_product = x1*x2 + y1*y2;
  return acos(dot_product);
}
2
votes

I had been looking for a microcontroller solution like this for gearbox seeking for an animatronic puppet and I didn't have the grunt to calculate trig properly.

@ruakh's answer was a good basis but I founded that the sign was incorrectly flipped in certain conditions.

Here's the solution that worked for me. This solution works for degree marks in a circle but changing MAX_VALUE allows this to work for an arbitrary max range that's useful when measuring gear encoder pulses.

Tested on Arduino.

#define MAX_VALUE 360

float shortestSignedDistanceBetweenCircularValues(float origin, float target){

  float signedDiff = 0.0;
  float raw_diff = origin > target ? origin - target : target - origin;
  float mod_diff = fmod(raw_diff, MAX_VALUE); //equates rollover values. E.g 0 == 360 degrees in circle

  if(mod_diff > (MAX_VALUE/2) ){
    //There is a shorter path in opposite direction
    signedDiff = (MAX_VALUE - mod_diff);
    if(target>origin) signedDiff = signedDiff * -1;
  } else {
    signedDiff = mod_diff;
    if(origin>target) signedDiff = signedDiff * -1;
  }

  return signedDiff;

}
0
votes

You'll need to import the math library of course, for fmod and fabs.

double a = -528.2;
double b = 740.0;
double diff = (a > b ? a - b : b - a);
double mod_diff = fmod(diff, 360);
double result = (mod_diff < 180 ? mod_diff : 360 - mod_diff);
0
votes

You can apply the formula you find here ( http://en.wikipedia.org/wiki/Arc_(geometry) ) on both angles and on both directions. So you find the two complementary distances (if you sum them up you will get the circumference (or you can get the length of one arc by subtracting the length of the other arc from the circumference).

You can then compare the two lengths to get the minimum distance between the two points at different angles.

In C++ you've got the math.h library: http://www.cplusplus.com/reference/clibrary/cmath/

0
votes

I had a similar problem for finding

  • Shortest distance from any point to any point in a circle. I obtained the solution as follows:

if N = number of points in the circle

                0 -> N-1
        j before n/2 after (n-j)

               1 -> N-1
   (j-1) before [(n/2)+1] after n-j+1

               2 -> N-1
   (j-2) before [(n/2)+2] after n-j+2

               and so on

where j is second point and i is first point

Here is little python code for the solution.

for i in range(0, n):
   for j in range(i,n):
          if j < n/2+i:
                 s_rt = j-i
          else :
                 s_rt = n-j+i

I think this can be used to find the solution by making slight adjustment to degree.

0
votes

For beginners like me, the other answers - though very likely to give you a good result - were a bit difficult to understand what is going on. Therefore, here is my method that checks which direction (clockwise or counter-clockwise) is shortest between a cp(current point) and a tp(target point). It also assigns that shortest distance value to a shortestDistance variable. Based on what I specifically needed this method for, it was important for me to return a boolean value as to whether the shortest distance was in the clockwise or counter-clockwise direction. Here is the method:

public boolean checkIfClockwiseIsShortest(int cp, int tp) {
    boolean clockwiseIsShortest = false;
    if (cp != tp) { // if current point and target point are not the same...
        if (tp > cp) { // if current point is less than target point AND..
            if ((tp - cp) <= ((360 - tp) + cp)) {
                clockwiseIsShortest = true;
                shortestDistance = (tp-cp);
                System.out.println("Case A: " + shortestDistance +" degrees clockwise");//[CAN REMOVE]

            } else if ((tp - cp) > ((360 - tp) + cp)) {
                clockwiseIsShortest = false;
                shortestDistance = ((360 - tp) + cp);
                System.out.println("Case B: " + shortestDistance+" degrees counter-clockwise");//[CAN REMOVE]
            }
        } else { // BUT if target point < current point
            if ((cp - tp) <= ((360 - cp) + tp)) {
                clockwiseIsShortest = false;
                shortestDistance = (cp-tp);
                System.out.println("Case C: " + shortestDistance+" degrees counter-clockwise");//[CAN REMOVE]
            } else if ((cp - tp) > ((360 - cp) + tp)) {
                clockwiseIsShortest = true;
                shortestDistance = ((360 - cp) + tp);
                System.out.println("Case D: " + shortestDistance+" degrees clockwise");//[CAN REMOVE]
            }
        }
    }
    return clockwiseIsShortest;
}

Note that cp is the starting point in integer degrees and tp is the target point in integer degrees; the type can be changed to double for more precision .

It does account for going (counter)clockwise past 0 degree mark.

The 3 if's it checks:

  1. is the starting/current point (cp) equal to target point(tp)? if so, no distance is given.
  2. if not equal, is the target point > current point in degrees OR is target point < current point in degrees?
  3. Based on this, the last if in the nest checks if going clockwise is less degrees that going counter-clockwise (or vise versa).

Again, the other posts with shorter code may work just fine.

Edit: BTW, the shortestDistance variable is a class variable, initialized within the same class as this method; if you are ripping this code, make sure that your class in which you are placing it also has a shortestDistance variable based on the primitive type as the cp and tp variables (or cast it).

-1
votes

You can try getting the absolute value of the difference of the remainders two angles when divided by 360.

#include <iostream>
#include <cmath>

using namespace std;
int degree_difference(int a, int b)
{
    return abs(a%360-b%360);
}

int main()
{
    int result = degree_difference(-235, 15);
    cout << "Difference: " << result << endl;
    return 0;
}
-1
votes

We have to assume that a circle only has 360 degrees, otherwise it's going to get tricky.

So, the first thing you have to do is get each mark to be within 0 to 360. To do this you can take the modulus of both marks by 360. If the amount is less than 0 then add 360.

Let's say our points are 520 and -45.

mark1 = ((520 % 360) >= 0) ? (520 % 360) : 360 - (520 % 360);

mark2 = ((-45 % 360) >= 0) ? (-45 % 360) : 360 - (-45 % 360);

mark1 will be 160. mark 2 will be 315.

Now you simply take the absolute value of the difference:

result = abs(mark1 - mark2) = 155