C: How to wrap a float to the interval [-pi, pi)

2019-01-08 07:46发布

I'm looking for some nice C code that will accomplish effectively:

while (deltaPhase >= M_PI) deltaPhase -= M_TWOPI;
while (deltaPhase < -M_PI) deltaPhase += M_TWOPI;

What are my options?

15条回答
孤傲高冷的网名
2楼-- · 2019-01-08 08:19

Here is a version for other people finding this question that can use C++ with Boost:

#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/sign.hpp>

template<typename T>
inline T normalizeRadiansPiToMinusPi(T rad)
{
  // copy the sign of the value in radians to the value of pi
  T signedPI = boost::math::copysign(boost::math::constants::pi<T>(),rad);
  // set the value of rad to the appropriate signed value between pi and -pi
  rad = fmod(rad+signedPI,(2*boost::math::constants::pi<T>())) - signedPI;

  return rad;
} 

C++11 version, no Boost dependency:

#include <cmath>

// Bring the 'difference' between two angles into [-pi; pi].
template <typename T>
T normalizeRadiansPiToMinusPi(T rad) {
  // Copy the sign of the value in radians to the value of pi.
  T signed_pi = std::copysign(M_PI,rad);
  // Set the value of difference to the appropriate signed value between pi and -pi.
  rad = std::fmod(rad + signed_pi,(2 * M_PI)) - signed_pi;
  return rad;
}
查看更多
Root(大扎)
3楼-- · 2019-01-08 08:20

I would do this:

double wrap(double x) {
    return x-2*M_PI*floor(x/(2*M_PI)+0.5);  
}

There will be significant numerical errors. The best solution to the numerical errors is to store your phase scaled by 1/PI or by 1/(2*PI) and depending on what you are doing store them as fixed point.

查看更多
Juvenile、少年°
4楼-- · 2019-01-08 08:20

I have used (in python):

def WrapAngle(Wrapped, UnWrapped ):
    TWOPI = math.pi * 2
    TWOPIINV = 1.0 / TWOPI
    return  UnWrapped + round((Wrapped - UnWrapped) * TWOPIINV) * TWOPI

c-code equivalent:

#define TWOPI 6.28318531

double WrapAngle(const double dWrapped, const double dUnWrapped )
{   
    const double TWOPIINV = 1.0/ TWOPI;
    return  dUnWrapped + round((dWrapped - dUnWrapped) * TWOPIINV) * TWOPI;
}

notice that this brings it in the wrapped domain +/- 2pi so for +/- pi domain you need to handle that afterward like:

if( angle > pi):
    angle -= 2*math.pi
查看更多
劳资没心,怎么记你
5楼-- · 2019-01-08 08:22

deltaPhase -= floor(deltaPhase/M_TWOPI)*M_TWOPI;

查看更多
该账号已被封号
6楼-- · 2019-01-08 08:23

In C99:

float unwindRadians( float radians )
{
   const bool radiansNeedUnwinding = radians < -M_PI || M_PI <= radians;

   if ( radiansNeedUnwinding )
   {
      if ( signbit( radians ) )
      {
         radians = -fmodf( -radians + M_PI, 2.f * M_PI ) + M_PI;
      }
      else
      {
         radians = fmodf( radians + M_PI, 2.f * M_PI ) - M_PI;
      }
   }

   return radians;
}
查看更多
ら.Afraid
7楼-- · 2019-01-08 08:24

If ever your input angle can reach arbitrarily high values, and if continuity matters, you can also try

atan2(sin(x),cos(x))

This will preserve continuity of sin(x) and cos(x) better than modulo for high values of x, especially in single precision (float).

Indeed, exact_value_of_pi - double_precision_approximation ~= 1.22e-16

On the other hand, most library/hardware use a high precision approximation of PI for applying the modulo when evaluating trigonometric functions (though x86 family is known to use a rather poor one).

Result might be in [-pi,pi], you'll have to check the exact bounds.

Personaly, I would prevent any angle to reach several revolutions by wrapping systematically and stick to a fmod solution like the one of boost.

查看更多
登录 后发表回答