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

前端 未结 15 790
死守一世寂寞
死守一世寂寞 2020-11-28 21:47

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

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


        
相关标签:
15条回答
  • 2020-11-28 22:27

    Instead of working in radians, use angles scaled by 1/(2π) and use modf, floor etc. Convert back to radians to use library functions.

    This also has the effect that rotating ten thousand and a half revolutions is the same as rotating half then ten thousand revolutions, which is not guaranteed if your angles are in radians, as you have an exact representation in the floating point value rather than summing approximate representations:

    #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';
    }}
    
    0 讨论(0)
  • 2020-11-28 22:30

    If linking against glibc's libm (including newlib's implementation) you can access __ieee754_rem_pio2f() and __ieee754_rem_pio2() private functions:

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

    Edit: Just realised that you need to link to libm.a, I couldn't find the symbols declared in libm.so

    0 讨论(0)
  • 2020-11-28 22:31

    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;
    }
    
    0 讨论(0)
  • 2020-11-28 22:33

    I encountered this question when searching for how to wrap a floating point value (or a double) between two arbitrary numbers. It didn't answer specifically for my case, so I worked out my own solution which can be seen here. This will take a given value and wrap it between lowerBound and upperBound where upperBound perfectly meets lowerBound such that they are equivalent (ie: 360 degrees == 0 degrees so 360 would wrap to 0)

    Hopefully this answer is helpful to others stumbling across this question looking for a more generic bounding solution.

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

    A related question for integers is available here: Clean, efficient algorithm for wrapping integers in C++

    0 讨论(0)
  • 2020-11-28 22:34

    A two-liner, non-iterative, tested solution for normalizing arbitrary angles to [-π, π):

    double normalizeAngle(double angle)
    {
        double a = fmod(angle + M_PI, 2 * M_PI);
        return a >= 0 ? (a - M_PI) : (a + M_PI);
    }
    

    Similarly, for [0, 2π):

    double normalizeAngle(double angle)
    {
        double a = fmod(angle, 2 * M_PI);
        return a >= 0 ? a : (a + 2 * M_PI);
    }
    
    0 讨论(0)
  • 2020-11-28 22:35

    There is also fmod function in math.h but the sign causes trouble so that a subsequent operation is needed to make the result fir in the proper range (like you already do with the while's). For big values of deltaPhase this is probably faster than substracting/adding `M_TWOPI' hundreds of times.

    deltaPhase = fmod(deltaPhase, M_TWOPI);
    

    EDIT: I didn't try it intensively but I think you can use fmod this way by handling positive and negative values differently:

        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;
    

    The computational time is constant (unlike the while solution which gets slower as the absolute value of deltaPhase increases)

    0 讨论(0)
提交回复
热议问题