Shortest distance between two degree marks on a circle?
Asked Answered
A

10

12

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?

Ace answered 29/2, 2012 at 20:9 Comment(4)
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.Acedia
It sounds like you take the absolute value of both numbers, and subtract the smallest from the largest, based on your examples.Copyreader
@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.Oratory
@ChrisLively: Yes, I realized you need a modulus calculation.Copyreader
G
22
  • 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)
Grindle answered 29/2, 2012 at 20:20 Comment(2)
This works great. Could it be modified to return a negative distance if the shortest distance from first to second is counter-clockwise and a positive number if it is clockwise?Striker
@neoflash: That can be done, but it's bigger than fits in a comment.Grindle
P
3

I had been looking for a microcontroller solution like this for gearbox seeking that didn't use any trig (for speed).

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

Below is the solution that I used. This solution works as is for circle degrees (i.e. 360 degrees in 1 degree increments) but changing MAX_VALUE allows this to work for an arbitrary max range - useful when measuring gear encoder pulses rather than degrees.

Return number is signed to indicate the direction to drive for shortest path to target.

Tested with Arduino IDE on Teensy 4.0.

#define MAX_VALUE 360

float shortestSignedDistanceBetweenTwoPointsOnCircle(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;
  
}
Prady answered 6/1, 2019 at 13:16 Comment(0)
O
2

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);
}
Outwards answered 29/2, 2012 at 20:22 Comment(3)
That is mathematically sound, but computationally intensive, and generates a lot of needless round-off error. A fabs+fmod approach runs about 20 times faster and generates orders of magnitude less round-off.Grindle
Additionally, you can use a trigonometric identity: (sin(a)sin(b)+cos(a)cos(b) = sin(a-b) to derive the solution: acos(sin(angle1 - angle2)).Creditable
sin(a)sin(b)+cos(a)cos(b) should be cos(a-b), not sin(a-b). See here.Malaguena
M
1

If you are on 0-360 circle, I think you can avoid fmod() and abs(). The following will give you the shortest distance (and direction) on a 360 degree circle without the need to include <math.h>. Direction at 180 and 0 will depend on which you consider the first number. I guess this works for circles on a non-360 degree scale (change 360 to the max value and 180 to max_value/2) but I haven't tested it. Have tested below code as-is for different values and it seems to work but best to not trust it--I don't.

float find_shortest_angle(float first, float second)
{
   //first = 350., second = 10.; //example rollover 350 to 10 = 20 deg
   first = 10., second = 350.; //example rollover 10 to 350 = -20 deg

   float diff, sweep_angle;

   diff = first-second;
   if(diff<-180)
      sweep_angle= -(360+diff);
   else if(diff>180)
      sweep_angle= 360-diff;
   else
      sweep_angle= -diff;

   return sweep_angle;
}
Middleman answered 22/8, 2023 at 3:52 Comment(1)
Probably possible to flip the greater than/less than symbols which would help eliminate some negative signs but I haven't tried that.Middleman
U
0

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/

Unfriended answered 29/2, 2012 at 20:15 Comment(1)
try it out. i suggest you to do it on paper first and then implement it in C++.Unfriended
P
0

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);
Prelatism answered 29/2, 2012 at 20:16 Comment(4)
That's not quite right. If (say) a is 0 and b is 200, your code will give 20 rather 160.Grindle
Thanks for the answer, but I found a counterexample. Say a = 10 and b = 270. a is less than b so the answer would be (270 - 10) mod 180. But 260 mod 180 is 80 but the actual distance is 100.Ace
There we go, I actually (shame on me) tested this code and now it gives me 171.8 for the result.Prelatism
And also works for a=10, b=270, result=100. And behaves properly with a & b switched.Prelatism
G
0

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.

Gillen answered 4/2, 2018 at 3:50 Comment(0)
I
0

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

Inhere answered 29/5, 2019 at 8:38 Comment(0)
G
-1

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;
}
Gdynia answered 29/2, 2012 at 20:26 Comment(1)
Hang on, I don't think this is what you want at all.Gdynia
O
-1

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
Oratory answered 29/2, 2012 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.