I have a problem with a piece of code that I wrote to wrap an angle around during an integration and is part of a small simulation that I'm working on. So basically the idea is to prevent the angle from growing large by making sure that it always has a sane value. I have tried three different approaches that I would expect to give the same results. And most of the time they do. But the first two give artifacts around the point where angle value wraps around. When I then generate a waveform from the angle value I get undesirable results because of these precision errors.
So the first approach is like this (limit angle to -8PI +8PI range):
self->state.angle = atan2f(sinf(angle / 8), cosf(angle / 8)) * 8;
This creates artifact that looks like this:
Second approach of:
self->state.angle = fmodf(angle, (float)(2.f * M_PI * 8))
However if I just do it like this:
float limit = (8 * 2 * M_PI);
if(angle > limit) angle -= limit;
if(angle < 0) angle += limit;
self->state.angle = a;
Then it works as expected without any artifacts:
So what am I missing here? Why do the other two approaches create precision error? I would expect all of them to generate the same result (I know that ranges of the angle are different but when the angle is passed further into a sin function I would expect the result to be the same).
Edit: small test
// g++ -o test test.cc -lm && ./test
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
int main(int argc, char **argv){
float a1 = 0;
float a2 = 0;
float a3 = 0;
float dt = 1.f / 7500.f;
for(float t = -4.f * M_PI; t < (4.f * M_PI); t+=dt){
a1 += dt;
a2 += dt;
a3 += dt;
float b1 = a1;
if(b1 > 2.f * M_PI) b1 -= 2.f * M_PI;
if(b1 < 0.f) b1 += 2.f * M_PI;
float b2 = atan2f(sinf(a2), cosf(a2));
float b3 = fmodf(a3, 2 * M_PI);
float x1 = sinf(b1);
float x2 = sinf(b2);
float x3 = sinf(b3);
if((x1 * x2 * x3) > 1e-9){
printf("%f: x[%f %f %f],\tx1-x2:%f x1-x3:%f x2-x3:%f]\n", t, x1, x2, x3, (x1 - x2) * 1e9, (x1 - x3) * 1e9, (x2 - x3) * 1e9);
}
}
return 0;
}
Output:
-9.421306: x[0.001565 0.001565 0.001565], x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.421172: x[0.001431 0.001431 0.001431], x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.421039: x[0.001298 0.001298 0.001298], x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.420905: x[0.001165 0.001165 0.001165], x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-9.420772: x[0.001032 0.001032 0.001032], x1-x2:0.000000 x1-x3:0.000000 x2-x3:0.000000]
-6.275573: x[0.001037 0.001037 0.001037], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275439: x[0.001171 0.001171 0.001171], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275306: x[0.001304 0.001304 0.001304], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275172: x[0.001438 0.001438 0.001438], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.275039: x[0.001571 0.001571 0.001571], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.274905: x[0.001705 0.001705 0.001705], x1-x2:0.000000 x1-x3:174.855813 x2-x3:174.855813]
-6.274772: x[0.001838 0.001838 0.001838], x1-x2:0.116415 x1-x3:174.855813 x2-x3:174.739398]