Shortest distance between two degree marks on a circle?

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;

}

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

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