3

I have two functions that are intended to contain angles between (-180,180] and (-π,π]. The intent is that given any angle from -inf to +inf it will retain the equivalent angle in the intervals specified. For example the angle for 1550° is 110°.

public double WrapBetween180(double angle)
{
    return angle - 360d * Math.Round(angle / 360d, MidpointRounding.AwayFromZero);
}
public double WrapBetweenPI(double angle)
{
    const double twopi = 2d * Math.PI;
    return angle - twopi * Math.Round(angle / twopi, MidpointRounding.AwayFromZero);
}

which yields the following results

WrapBetween180(-180) = -180
WrapBetween180( 180) =  180

WrapBetweenPI(-Math.PI) =  Math.PI
WrapBetweenPI( Math.PI) = -Math.PI

none of which is what I want. What I wanted is:

WrapBetween180(-180) =  180
WrapBetween180( 180) =  180

WrapBetweenPI(-Math.PI) =  Math.PI
WrapBetweenPI( Math.PI) =  Math.PI

I tryied playing around with the rounding methods, but still cannot get the desired results. The problem is pronounced because sometimes the angles I deal with are only approximately close to -π or π and I am getting discontinuities it my results.

Any suggestions on how to best implement angle wrapping functions with non-inclusive low limit and inclusive high limits?

John Alexiou
  • 28,472
  • 11
  • 77
  • 133

3 Answers3

2

For the angle in degrees, if x is between -180 and 180, then 180 - x is between 0 and 360. What you want is equivalent to asking that 180 - x is between 0 (inclusive), and 360 (exclusive). So, as soon as 180 - x reaches 360, we want to add 360 to the angle. This gives us:

return angle + 360d * Math.Floor((180d - angle) / 360d);

Same thing for the angle in radians:

return angle + twopi * Math.Floor((Math.PI - angle) / twopi);
Jeffrey Sax
  • 10,253
  • 3
  • 29
  • 40
0

It does not address the rounding issue, but here is how I would to what you want to do :

private static double ConvertAngle(double angle)
{
    bool isNegative = angle < 0;
    if (isNegative)
        angle *= -1;

    angle = angle % 360;
    if (isNegative)
        angle = -1 * angle + 360;

    if (angle > 180)
        angle = (angle - 360);

    return angle;
}

Note: This way supposes you want "behind" to be 180 degrees, not -180 degrees.

Tipx
  • 7,367
  • 4
  • 37
  • 59
0

Isn't this a case for a modulo operation?

private double Wrap180(double value)
{
    // exact rounding of corner values
    if (value == 180) return 180.0;
    if (value == -180) return 180.0;

    // "shift" by 180 and use module, then shift back.
    double wrapped = ((Math.Abs(value) + 180.0) % 360.0) - 180.0;

    // handle negative values correctly
    if (value < 0) return -wrapped;
    return wrapped;
}

It passes this tests

    Assert.AreEqual(170.0, wrap(-190.0));
    Assert.AreEqual(180.0, wrap(-180.0));
    Assert.AreEqual(-170.0, wrap(-170.0));
    Assert.AreEqual(0.0, wrap(0.0));
    Assert.AreEqual(10.0, wrap(10.0));
    Assert.AreEqual(170.0, wrap(170.0));
    Assert.AreEqual(180.0, wrap(180.0));
    Assert.AreEqual(-170.0, wrap(190.0));
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • I like the approach of using modulo, but I wonder how it performs relative to a `Math.Floor()` function for large numbers. Honestly I don't know which is the more robust approach. – John Alexiou Sep 15 '11 at 17:42