C:如何将浮点值包装到区间[-pi,pi)

C:如何将浮点值包装到区间[-pi,pi),c,math,floating-point,intervals,modulo,C,Math,Floating Point,Intervals,Modulo,我正在寻找一些优秀的C代码,可以有效地完成: while (deltaPhase >= M_PI) deltaPhase -= M_TWOPI; while (deltaPhase < -M_PI) deltaPhase += M_TWOPI; while(deltaPhase>=M_PI)deltaPhase-=M_two PI; 而(deltaPhaseM_-PI){ r-=2*M_PI; } 而(r0.5)r-=1; 如果(r编辑2013年4月19日: 更新模函数以处理边界情

我正在寻找一些优秀的C代码,可以有效地完成:

while (deltaPhase >= M_PI) deltaPhase -= M_TWOPI;
while (deltaPhase < -M_PI) deltaPhase += M_TWOPI;
while(deltaPhase>=M_PI)deltaPhase-=M_two PI;
而(deltaPhase<-M_-PI)deltaPhase+=M_-PI;

我的选择是什么?

deltaPhase-=floor(deltaPhase/M_TWOPI)*M_TWOPI;
math.h
中也有
fmod
函数,但是符号会引起麻烦,因此需要后续操作才能使结果在正确的范围内(就像您已经使用while的那样)。对于
deltaPhase
的大值,这可能比减去/添加“M_TWOPI”数百次要快

deltaPhase = fmod(deltaPhase, M_TWOPI);
编辑: 我没有深入地尝试,但我认为您可以通过不同地处理正值和负值来使用
fmod

    if (deltaPhase>0)
        deltaPhase = fmod(deltaPhase+M_PI, 2.0*M_PI)-M_PI;
    else
        deltaPhase = fmod(deltaPhase-M_PI, 2.0*M_PI)+M_PI;

计算时间是恒定的(与while解不同,while解随着deltaPhase绝对值的增加而变慢)

使用1/(2π)缩放的角度,并使用modf、floor等。转换回弧度以使用库函数

这也会产生这样的效果:旋转一万半圈与旋转一万半圈相同,如果角度以弧度为单位,则无法保证这一点,因为在浮点值中有精确表示,而不是求和近似表示:

#include <iostream>
#include <cmath>

float wrap_rads ( float r )
{
    while ( r > M_PI ) {
        r -= 2 * M_PI;
    }

    while ( r <= -M_PI ) {
        r += 2 * M_PI;
    }

    return r;
}
float wrap_grads ( float r )
{
    float i;
    r = modff ( r, &i );

    if ( r > 0.5 ) r -= 1;
    if ( r <= -0.5 ) r += 1;

    return r;
}

int main ()
{
    for (int rotations = 1; rotations < 100000; rotations *= 10 ) {
    {
        float pi = ( float ) M_PI;
        float two_pi = 2 * pi;

        float a = pi;
        a += rotations * two_pi;

        std::cout << rotations << " and a half rotations in radians " << a << " => " << wrap_rads ( a ) / two_pi << '\n' ;
    }
    {
        float pi = ( float ) 0.5;
        float two_pi = 2 * pi;

        float a = pi;
        a += rotations * two_pi;

        std::cout << rotations << " and a half rotations in grads " << a << " => " << wrap_grads ( a ) / two_pi << '\n' ;
    }
    std::cout << '\n';
}}
#包括
#包括
浮子缠绕半径(浮子r)
{
while(r>M_-PI){
r-=2*M_PI;
}
而(r0.5)r-=1;

如果(r编辑2013年4月19日:

更新模函数以处理边界情况,如aka.nice和arr_sea所述:

static const double     _PI= 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348;
static const double _TWO_PI= 6.2831853071795864769252867665590057683943387987502116419498891846156328125724179972560696;

// Floating-point modulo
// The result (the remainder) has same sign as the divisor.
// Similar to matlab's mod(); Not similar to fmod() -   Mod(-3,4)= 1   fmod(-3,4)= -3
template<typename T>
T Mod(T x, T y)
{
    static_assert(!std::numeric_limits<T>::is_exact , "Mod: floating-point type expected");

    if (0. == y)
        return x;

    double m= x - y * floor(x/y);

    // handle boundary cases resulted from floating-point cut off:

    if (y > 0)              // modulo range: [0..y)
    {
        if (m>=y)           // Mod(-1e-16             , 360.    ): m= 360.
            return 0;

        if (m<0 )
        {
            if (y+m == y)
                return 0  ; // just in case...
            else
                return y+m; // Mod(106.81415022205296 , _TWO_PI ): m= -1.421e-14 
        }
    }
    else                    // modulo range: (y..0]
    {
        if (m<=y)           // Mod(1e-16              , -360.   ): m= -360.
            return 0;

        if (m>0 )
        {
            if (y+m == y)
                return 0  ; // just in case...
            else
                return y+m; // Mod(-106.81415022205296, -_TWO_PI): m= 1.421e-14 
        }
    }

    return m;
}

// wrap [rad] angle to [-PI..PI)
inline double WrapPosNegPI(double fAng)
{
    return Mod(fAng + _PI, _TWO_PI) - _PI;
}

// wrap [rad] angle to [0..TWO_PI)
inline double WrapTwoPI(double fAng)
{
    return Mod(fAng, _TWO_PI);
}

// wrap [deg] angle to [-180..180)
inline double WrapPosNeg180(double fAng)
{
    return Mod(fAng + 180., 360.) - 180.;
}

// wrap [deg] angle to [0..360)
inline double Wrap360(double fAng)
{
    return Mod(fAng ,360.);
}
static const double _PI=3.141592653589793238462643383279502884197169399375105820974944592307816406286208986280348;
静态常数double _TWO_PI=6.2831853071795864769252867665590057639433879875021164194988918461563282125724179972560696;
//浮点模
//结果(余数)与除数具有相同的符号。
//与matlab的mod()类似;与fmod()不同-mod(-3,4)=1 fmod(-3,4)=-3
模板
T模(T x,T y)
{
static_assert(!std::numeric_limits::is_exact,“应该是Mod:浮点类型”);
如果(0.==y)
返回x;
双m=x-y*楼层(x/y);
//处理浮点截断导致的边界情况:
if(y>0)//模范围:[0..y)
{
如果(m>=y)//Mod(-1e-16360.):m=360。
返回0;

if(m我在搜索如何包装浮点值(或双精度浮点值)时遇到了这个问题在两个任意数字之间。它不是专门针对我的情况的,所以我制定了我自己的解决方案,可以在这里看到。这将取一个给定的值,并将其包装在下限和上限之间,其中上限完全满足下限,使其相等(即:360度==0度,因此360度将包装为0)

希望这个答案有助于其他人在这个问题上寻找更通用的边界解决方案

double boundBetween(double val, double lowerBound, double upperBound){
   if(lowerBound > upperBound){std::swap(lowerBound, upperBound);}
   val-=lowerBound; //adjust to 0
   double rangeSize = upperBound - lowerBound;
   if(rangeSize == 0){return upperBound;} //avoid dividing by 0
   return val - (rangeSize * std::floor(val/rangeSize)) + lowerBound;
}
有关整数的相关问题,请参见:

我使用过(在python中):

等效c代码:

#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;
}
请注意,这会将其放入包装域+/-2pi中,因此对于+/-pi域,您需要像下面这样处理它:

if( angle > pi):
    angle -= 2*math.pi
如果fmod()是通过截断除法实现的,并且与符号相同,则可以利用它来解决一般问题:

对于(-PI,PI)的情况:

if (x > 0) x = x - 2PI * ceil(x/2PI)  #Shift to the negative regime
return fmod(x - PI, 2PI) + PI
对于[-PI,PI]的情况:

如果(x<0)x=x-2PI*floor(x/2PI)#转换到正状态
返回fmod(x+PI,2PI)-PI

(注意这是伪代码,我的原件是用TCL写的,我不想用它折磨每个人。我需要第一个案例,所以必须弄清楚这个问题。)< /P> < P>这是一个版本,其他人发现这个问题,可以用C++与Booost:

#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;
} 
#包括
#包括
模板
内联T标准化
{
//将弧度值的符号复制到pi值
T signedPI=boost::math::copysign(boost::math::constants::pi(),rad);
//将rad的值设置为pi和-pi之间的适当有符号值
rad=fmod(rad+signedPI,(2*boost::math::constants::pi())-signedPI;
返回rad;
} 
C++11版本,无Boost依赖项:

#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;
}
#包括
//将两个角度之间的“差”带入[-pi;pi]。
模板
T标准化平均值(T rad){
//将弧度值的符号复制到pi值。
T符号=std::copysign(M符号,rad);
//将差值设置为pi和-pi之间的适当有符号值。
rad=std::fmod(rad+signed_-pi,(2*M_-pi))-signed_-pi;
返回rad;
}

如果您的输入角度可以达到任意高的值,并且如果连续性很重要,您也可以尝试

atan2(sin(x),cos(x))
对于高x值,尤其是在单精度(浮点)下,这将比模更好地保持sin(x)和cos(x)的连续性

实际上,双精度近似的精确值=1.22e-16

另一方面,在计算三角函数时,大多数库/硬件使用PI的高精度近似值来应用模(尽管已知x86系列使用的是相当差的模)

结果可能是[-pi,pi],您必须检查精确的边界

就我个人而言,我会通过系统地包装来防止任何角度达到几圈,并坚持使用类似boost的fmod解决方案。

我会这样做:

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

数值误差较大。数值误差的最佳解决方案是按1/PI或1/(2*PI)的比例存储相位根据您正在做的事情,将它们存储为固定点。

您建议的方式是最好的。对于小偏转来说,它是最快的。如果程序中的角度不断偏转到适当的范围,那么您应该只会遇到大的偏转
atan2(sin(x),cos(x))
double wrap(double x) {
    return x-2*M_PI*floor(x/(2*M_PI)+0.5);  
}
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;
}
/* change to `float/fmodf` or `long double/fmodl` or `int/%` as appropriate */

/* wrap x -> [0,max) */
double wrapMax(double x, double max)
{
    /* integer math: `(max + x % max) % max` */
    return fmod(max + fmod(x, max), max);
}
/* wrap x -> [min,max) */
double wrapMinMax(double x, double min, double max)
{
    return min + wrapMax(x - min, max - min);
}
extern __int32_t __ieee754_rem_pio2f (float,float*);

float wrapToPI(float xf){
const float p[4]={0,M_PI_2,M_PI,-M_PI_2};

    float yf[2];
    int q;
    int qmod4;

    q=__ieee754_rem_pio2f(xf,yf);

/* xf = q * M_PI_2 + yf[0] + yf[1]                 /
 * yf[1] << y[0], not sure if it could be ignored */

    qmod4= q % 4;

    if (qmod4==2) 
      /* (yf[0] > 0) defines interval (-pi,pi]*/
      return ( (yf[0] > 0) ?  -p[2] : p[2] ) + yf[0] + yf[1];
    else
      return p[qmod4] + yf[0] + yf[1];
}
double normalizeAngle(double angle)
{
    double a = fmod(angle + M_PI, 2 * M_PI);
    return a >= 0 ? (a - M_PI) : (a + M_PI);
}
double normalizeAngle(double angle)
{
    double a = fmod(angle, 2 * M_PI);
    return a >= 0 ? a : (a + 2 * M_PI);
}