atan2f vs fmodf vs just plain subtraction - c

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))
Creates the same result:
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]

Without more information it's difficult to provide an explanation but I'll try anyway.
The difference between using fmod and "plain subtraction" (or addition) like you're doing is that if the value is way out of range already (like 800000 * M_PI for instance), then the add/subtract method doesn't change the value much (it has little effect) and a very big (in absolute value) angle hits your computation function, without an issue, since no artifact is seen.
Using fmod (or atan2) guarantees that the value is in the range you defined, which isn't the same thing.
Note that doing:
float limit = (8 * 2 * M_PI);
while(angle > limit) angle -= limit;
while(angle < 0) angle += limit;
self->state.angle = a;
would be equivalent (roughly) to fmod (but would be worse than fmod for big values since it introduces floating point accumulation errors because of repeated additions or subtractions).
So if inputting very big values in your computation produces the correct result, then you can wonder if it's wise to normalize your angles instead of leaving that to the math library.
EDIT: the first part of this answer assumed that this super-out-of-bounds case would happen, and further question edits showed that this wasn't the case, so...
The other difference between fmod and 2 tests is that there's no guaranteed that the value is the same if already in range when calling fmod
For instance if the implementation is like value - int(value/modulus)*modulus;, floating point inaccuracy may substract a small value to the original value.
Using atan2f combined with sin ... also changes the result if already in range.
(and even if the value is slightly out of range, adding/subbing like you're doing doesn't involve dividing/truncating/multiplying)
Since you can adjust the value in the range by just adding or subbing once, using fmodf or atan2f is overkill in your case, and you can stick to your simple sub/add (adding an else would save a test: if you just reajusted a too low value, no need to test to see if the value is too big)

float versus double math.
Of course the 3rd method works best. It is using double math.
Look at b1, b3. b3 is certainly calculated with float precision due to the fmodf() call.
Note that M_PI is usually a double, so b1 -= 2.f * M_PI; is likely done with double precision math and provides a more accurate answer. The f in 2.f does not force the product 2.f * M_PI into float - the product is double and so is -=.
b1 -= 2.f * M_PI;
// same as
b1 = (float)((double)b1 - (2.f * M_PI));
Further: with optimizations and FLT_EVAL_METHOD > 0, C is allowed to perform FP code at higher than the precision of the type. b1 may calculate at double, even though code appears float. With higher precision, and the fact that M_PI (a rational number) is not exactly π (an irrational number), leads to a more accurate b1 than fmodf(a3, 2 * M_PI);
float b1 = a1;
if(b1 > 2.f * M_PI) b1 -= 2.f * M_PI; // double math
if(b1 < 0.f) b1 += 2.f * M_PI; // double math
float b3 = fmodf(a3, 2 * M_PI);
To insure float results, use volatile float b1 = a1; to do a fair comparison and use float constants like #define M_PIf ((float) M_PI)
Further. With a fair comparison, better to use if(b1 < -2.f * M_PIf) b1 += 2.f * M_PIf;
Recommend OP print FLT_EVAL_METHOD to aid further discussion.
#include <float.h>
printf("%d\n", FLT_EVAL_METHOD);
OP has 2 solutions:
Use wider math like double for the sensitive radian reduction.
float b3 = fmod(a3, 2 * M_PI); // not fmodf
Do not use radians, but an angle measurement like degrees or BAM and perform exact range reduction. Angles will need degrees to radian conversions prior to trig calls.
float b3 = fmodf(a3, 360.0f); // use fmodf, a3, b3 are in degrees
Note: the float b2 = atan2f(sinf(a2), cosf(a2)); method is not a reasonable contender.

Related

How can I round a number to the fourth decimal place without using round functions?

This is what I've tried so far but I cannot figure out how to round to the fourth decimal place:
162.3582 = (int)(162.3582 + 0.005);
printf("%.002f\n", 162.3582);
I'm trying to get 162.3600 but I'm getting 162.00 instead.
Maybe look at it like this (I'm just unpacking Steve Summit's comment):
double d = 162.3582; // original value
d *= 100; // shift decimal point right, giving 16235.82
// Note this is equivalent to Steve Summit's divide by .01
d += .5; // add one half, giving 16236.32
d = (int)d; // discard fraction, giving 16236.0
// Or use floor(d) as Steve Summit suggested (he's a genuine C expert)
d /= 100.0 // shift decimal point back, giving 162.36
// Equivalent to Summit's * 0.01
How can I round a number to the fourth decimal place without using round functions?
The trick is usually to scale the value by 104, add 0.5, cast to some integer type, and the divide 104
// Weak code
double pow10 = 10000.0;
double r1 = x * pow10;
r1 += 0.5;
int i = (int) r1;
double r2 = i / pow10;
Problems:
x * pow10 may overflow.
x * pow10 may form an inexact product, tainting further calculations.
r1 += 0.5; may form an inexact sum, tainting further calculations.
r1 += 0.5; should be -0.5 for negative numbers.
int i = (int) r1; is undefined behavior when r1 is much outside int range.
i / pow10; may form an inexact quotient. Some values like 0.0001, may not be exactly representable as a double, yet the division should get closest.
Some improvements:
// a little better code
long double pow10 = 10000.0L;
long double r1 = x * pow10;
r1 = r1 + (signbit(x) ? -0.5 : 0.5);
long double i = truncl(r1); // not floor
double r2 = (double) (i / pow10);
Above code is just as weak when long double range/precision same as double.
If saddled with the dubious "without using round functions" requirement, consider printing:
sprintf(buf_of suffcient_size, "%.*f", 4, x);
double r2 = atof(buf_of suffcient_size);

wrong calculation when using float

I'm trying to do a calculation and for some reason when I'm using float I'm getting -nan(ind) but when I'm changing the variables (x,y) to double I'm getting to right answer maybe you guys have any idea why its happening ?
Thank you
#include <stdio.h>
#include <math.h>
#define pi 3.1416
#define L1 0.5
#define L2 0.5
void main()
{
float q1[12], q2[12], q1_Degrees[12], q2_Degrees[12];
float x = 0.8;
float y = 0.6;
q2[0] = acos((pow(x, 2) + pow(y, 2) - pow(L1, 2) - pow(L2, 2)) / (2 * L1*L2));
q1[0] = atan(y / x) - atan((L2*sin(q2[0])) / (L1 + L2 * cos(q2[0])));
q1_Degrees[0] = (q1[0] * 180) / pi;
q2_Degrees[0] = (q2[0] * 180) / pi;
printf_s("q1 is = %.1f q2 is = %.1f\n\n", q1_Degrees[0], q2_Degrees[0]);
}
2 concerns
acos()
The x in acos(x) needs to be in the range [-1...1]. Outside that, the result may be NaN.
(pow(x, 2) + pow(y, 2) - pow(L1, 2) - pow(L2, 2)) / (2 * L1*L2) is prone to slight effects of computation that result in a value just outside [-1...1] even if mathematically the result should be in range.
A quick work-around:
double z = (pow(x, 2) + pow(y, 2) - pow(L1, 2) - pow(L2, 2)) / (2 * L1*L2);
if (z < -1.0) z = -1.0;
else if (z > 1.0) z = 1.0;
q2[0] = acos(z);
The issue applies to double, float, long double. The fact it "worked" with one type is no reason to believe code is robust with other values.
Note that code is calling double functions like acos(), pow() and not their float counterparts acosf(), powf(). I recommend to use double throughout unless you have a compelling reason otherwise.
atan
atan() provides a [-π/2... +π/2] radians (aka [-90...90] degrees) result.
A whole circle result of [-π... +π] radians (aka [-180...180] degrees) is available with atan2(y,x)
atan((L2*sin(q2[0])) / (L1 + L2 * cos(q2[0])))
// or
atan2(L2*sin(q2[0]), L1 + L2 * cos(q2[0]))
A better solution is to use a different form of trig manipulation that does not depend on the edge of acos(). Easiest to do if OP also posted the higher level goal of the exercise.
Some basic debugging:
First, you can narrow down your code to:
#include <stdio.h>
#include <math.h>
void main()
{
float x = 0.8;
float y = 0.6;
double q = acos((pow(x, 2) + pow(y, 2) - 0.5) * 2);
printf("q = %lf\n", q);
}
Then, it becomes obvious that either pow(x, 2) or pow(y, 2) yield slightly different results for float and double.
At this point, let's investigate the actual differences:
Between the value of (float)0.8 and the value of (double)0.8
Between the value of (float)0.6 and the value of (double)0.6
#include <stdio.h>
void main()
{
printf("(float)0.8 = %.10f\n", (float)0.8);
printf("(double)0.8 = %.10lf\n", (double)0.8);
printf("(float)0.6 = %.10f\n", (float)0.6);
printf("(double)0.6 = %.10lf\n", (double)0.6);
}
The printout is:
(float)0.8 = 0.8000000119
(double)0.8 = 0.8000000000
(float)0.6 = 0.6000000238
(double)0.6 = 0.6000000000
Does that answer your question?
You're getting accumulated roundoff which runs a bit past the domain of acos().
Simplifying your example to a minimum that shows the issue:
#include <stdio.h>
#include <math.h>
#define L1 0.5
#define L2 0.5
int main()
{
float x = 0.8;
float y = 0.6;
float acos_param = (pow(x, 2) + pow(y, 2) - pow(L1, 2) - pow(L2, 2)) / (2 * L1*L2);
float q2 = acos(acos_param);
printf("acos_param = %.9f; q2 = %.9f\n", acos_param, q2);
return 0;
}
And running this - with floats - we see:
acos_param = 1.000000119; q2 = nan
Aha: greater than 1.0 is out of range of acos so you get NaN (not a number).
Changing all the float to double we get:
acos_param = 1.000000000; q2 = 0.000000000
which is more in line with expectations.
EDIT - Expanding on comments in the comments, variadic functions in C always pass floating-point values as double, and the misleadingly-named format %f really means double, not float.
Even if you attempt to cast "down" to a float, it will get promoted again back to double before it's called, but will truncate the precision.
Try this:
#include <stdio.h>
int main()
{
double d1 = 0.8;
double d2 = (float)0.8;
printf("d1=%.9f; d2=%.9f\n", d1, d2);
return 0;
}
Which on my compiler produces:
d1=0.800000000; d2=0.800000012
Here, d1 is the full real-deal double, while d2 is the float-truncated version promoted back to double.
And in no case is the l format specifier need; %f and %lf are the same thing.

Converting radians to degrees in a reference-circle manner

Context: I'm creating a map-replanner for a robot in C, it should add obstacles within the environment to the map depending on which IR-sensors are getting high readings.
For this i need to check how the robot is positioned IRL vs in the internal map (pointing north). But for some reason, i sometimes get negative values in the conversion. Any ideas what i'm doing wrong?
There should never be any negative values. The allowed values should lie between 0-360. As of now, i get negative values sometimes.
#define PI 3.14159265358979323846264338327950
#define DEG(x) (x*57.2957795130823208767981548141)
Code:
float currentRot = now.th; //current rotation-reading in RAD from function.
if(currentRot > PI){ //normalize values
currentRot = currentRot - 2 * PI;
}
if(currentRot < -PI){
currentRot = currentRot + 2 * PI;
}
currentRot = fmod(currentRot,PI*2); //convert into RAD-modular
currentRot = DEG(currentRot); //convert to degrees
Any ideas of what i'm doing wrong?
π cannot be exactly represented as a float, so any mod-ding with fmod(currentRot,PI*2); or comparing if(currentRot > PI){ may fail to provide the expected results for edge cases.
Note that currentRot = currentRot + 2 * PI; is a (foat) + (double) and the conversion to float. The edges of this conversion are problem to avoid slight negative results. Best to avoid mixing float and double math for this sensitive conversion.
Even with the good comment of #M Oehm, the inexactness PI and mixing float/double math can result in negative currentRot.
Instead, convert radians to degrees first and then mod.
float RadianToDegree(float r);
float d = (float)(PI/180.0) * r;
d = fmodf(d, 360.0f);
if (d < 0.0f) d += 360.0f;
return d;
}
The result will be [0.0 - 360.0] inclusive.
Mod-ding by 360 can be expected to incur to no round-off error.
Use fmodf() with float and fmod() with double to avoid unnecessary conversions.
On a higher level note, the goal of "The allowed values should lie between 0-360" sacrifice precision. Consider a primary range of between -180 - +180.
Here:
if(currentRot > PI){ //normalize values
currentRot = currentRot - 2 * PI;
}
if(currentRot < -PI){
currentRot = currentRot + 2 * PI;
}
you try to normalize to -π < currentRot < π, and then you do:
currentRot = fmod(currentRot,PI*2);
which "Returns the floating-point remainder of numer/denom" (ref), meaning that the sign of the result, will be the same.
This does not ensure that negative values are prohibited. I mean your range doesn't start from 0.

Why does this code return two different values doing the same?

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
double a;
double b;
double q0 = 0.5 * M_PI + 0.5 * -2.1500000405000002;
double q1 = 0.5 * M_PI + 0.5 * 0.0000000000000000;
double w0 = 0.5 * M_PI + 0.5 * -43000.0008100000050000;
double w1 = 0.5 * M_PI + 0.5 * -0.0000000000000000;
double m = 1;
double g = 43000000.81;
double l1 = 0.1;
double l2 = 0.1;
double h = 0.0001;
a = ((-g / l1) * sin(q0) + (sin(q1 - q0) * (cos(q1 - q0) * (w0 * w0 + (g / l1) * cos(q0)) + l2 * (w1 * w1 / l1))) / (m + pow(sin(q1 - q0), 2)));
a = h * a;
b = h * ((-g / l1) * sin(q0) + (sin(q1 - q0) * (cos(q1 - q0) * (w0 * w0 + (g / l1) * cos(q0)) + l2 * (w1 * w1 / l1))) / (m + pow(sin(q1 - q0), 2)));
printf("%.20lf ", a);
printf("%.20lf", b);
return 0;
}
I do the same calculations with a and b, just with the difference that I get the value of a in two steps, and the b in one.
My code returns:
-629.47620126173774000000 -629.47620126173763000000
What is the reason of the difference between the two last decimals?
The C Standard (99 and 11) says:
The values of operations with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type.
So in an expression such as h*(X+Y) as you have in the assignment to b, the implementation is allowed to use greater precision for the intermediate result of X+Y than can possibly be stored in a double, even though the type of the subexpression is still considered to be double. But in a=X+Y; a=h*a;, the first assignment forces the value to be one that actually can be stored in a double, causing a slightly different result.
Another possibility is that the compiler has done "floating point contraction". To quote the C Standard again,
A floating expression may be contracted, that is, evaluated as though it were an atomic operation, thereby omitting rounding errors implied by the source code and the expression evaluation method.
This would most likely happen if the processor has a single instruction that can do a floating point addition and then a multiplication in one step, and the compiler decided to use it.
Assuming one or both of these is the cause, your value b is likely a more accurate representation of the computation you specified (given that all the inputs were restricted to values which can be represented in a double).
The cppreference page about macro FLT_EVAL_METHOD discusses both of these issues in a little more detail. It may be interesting to find out your value of FLT_EVAL_METHOD and play with #pragma STDC FP_CONTRACT OFF.
The answer is because in the float numbers calculations the equation a=bcde does not have to be equal to x = bc y = de and a.= xy.
It is because floating point arithmetic has a limited precision.

How to compute sine wave with accuracy over the time

Use case is to generate a sine wave for digital synthesis, so, we need to compute all values of sin(d t) where:
t is an integer number, representing the sample number. This is variable. Range is from 0 to 158,760,000 for one hour sound of CD quality.
d is double, representing the delta of the angle. This is constant. And the range is: greater than 0 , less than pi.
Goal is to achieve high accuracy with traditional int and double data types. Performance is not important.
Naive implementation is:
double next()
{
t++;
return sin( ((double) t) * (d) );
}
But, the problem is when t increases, accuracy gets reduced because big numbers provided to "sin" function.
An improved version is the following:
double next()
{
d_sum += d;
if (d_sum >= (M_PI*2)) d_sum -= (M_PI*2);
return sin(d_sum);
}
Here, I make sure to provide numbers in range from 0 to 2*pi to the "sin" function.
But, now, the problem is when d is small, there are many small additions which decreases the accuracy every time.
The question here is how to improve the accuracy.
Appendix 1
"accuracy gets reduced because big numbers provided to "sin" function":
#include <stdio.h>
#include <math.h>
#define TEST (300000006.7846112)
#define TEST_MOD (0.0463259891528704262050786960234519968548937998410258872449766)
#define SIN_TEST (0.0463094209176730795999323058165987662490610492247070175523420)
int main()
{
double a = sin(TEST);
double b = sin(TEST_MOD);
printf("a=%0.20f \n" , a);
printf("diff=%0.20f \n" , a - SIN_TEST);
printf("b=%0.20f \n" , b);
printf("diff=%0.20f \n" , b - SIN_TEST);
return 0;
}
Output:
a=0.04630944601888796475
diff=0.00000002510121488442
b=0.04630942091767308033
diff=0.00000000000000000000
You can try an approach that is used is some implementations of fast Fourier transformation. Values of trigonometric function are calculated based on previous values and delta.
Sin(A + d) = Sin(A) * Cos(d) + Cos(A) * Sin(d)
Here we have to store and update cosine value too and store constant (for given delta) factors Cos(d) and Sin(d).
Now about precision: cosine(d) for small d is very close to 1, so there is risk of precision loss (there are only few significant digits in numbers like 0.99999987). To overcome this issue, we can store constant factors as
dc = Cos(d) - 1 = - 2 * Sin(d/2)^2
ds = Sin(d)
using another formulas to update current value
(here sa = Sin(A) for current value, ca = Cos(A) for current value)
ts = sa //remember last values
tc = ca
sa = sa * dc + ca * ds
ca = ca * dc - ts * ds
sa = sa + ts
ca = ca + tc
P.S. Some FFT implementations periodically (every K steps) renew sa and ca values through trig. functions to avoid error accumulation.
Example result. Calculations in doubles.
d=0.000125
800000000 iterations
finish angle 100000 radians
cos sin
described method -0.99936080743598 0.03574879796994
Cos,Sin(100000) -0.99936080743821 0.03574879797202
windows Calc -0.9993608074382124518911354141448
0.03574879797201650931647050069581
sin(x) = sin(x + 2N∙π), so the problem can be boiled down to accurately finding a small number which is equal to a large number x modulo 2π.
For example, –1.61059759 ≅ 256 mod 2π, and you can calculate sin(-1.61059759) with more precision than sin(256)
So let's choose some integer number to work with, 256. First find small numbers which are equal to powers of 256, modulo 2π:
// to be calculated once for a given frequency
// approximate hard-coded numbers for d = 1 below:
double modB = -1.61059759; // = 256 mod (2π / d)
double modC = 2.37724612; // = 256² mod (2π / d)
double modD = -0.89396887; // = 256³ mod (2π / d)
and then split your index as a number in base 256:
// split into a base 256 representation
int a = i & 0xff;
int b = (i >> 8) & 0xff;
int c = (i >> 16) & 0xff;
int d = (i >> 24) & 0xff;
You can now find a much smaller number x which is equal to i modulo 2π/d
// use our smaller constants instead of the powers of 256
double x = a + modB * b + modC * c + modD * d;
double the_answer = sin(d * x);
For different values of d you'll have to calculate different values modB, modC and modD, which are equal to those powers of 256, but modulo (2π / d). You could use a high precision library for these couple of calculations.
Scale up the period to 2^64, and do the multiplication using integer arithmetic:
// constants:
double uint64Max = pow(2.0, 64.0);
double sinFactor = 2 * M_PI / (uint64Max);
// scale the period of the waveform up to 2^64
uint64_t multiplier = (uint64_t) floor(0.5 + uint64Max * d / (2.0 * M_PI));
// multiplication with index (implicitly modulo 2^64)
uint64_t x = i * multiplier;
// scale 2^64 down to 2π
double value = sin((double)x * sinFactor);
As long as your period is not billions of samples, the precision of multiplier will be good enough.
The following code keeps the input to the sin() function within a small range, while somewhat reducing the number of small additions or subtractions due to a potentially very tiny phase increment.
double next() {
t0 += 1.0;
d_sum = t0 * d;
if ( d_sum > 2.0 * M_PI ) {
t0 -= (( 2.0 * M_PI ) / d );
}
return (sin(d_sum));
}
For hyper accuracy, OP has 2 problems:
multiplying d by n and maintaining more precision than double. That is answered in the first part below.
Performing a mod of the period. The simple solution is to use degrees and then mod 360, easy enough to do exactly. To do 2*π of large angles is tricky as it needs a value of 2*π with about 27 more bits of accuracy than (double) 2.0 * M_PI
Use 2 doubles to represent d.
Let us assume 32-bit int and binary64 double. So double has 53-bits of accuracy.
0 <= n <= 158,760,000 which is about 227.2. Since double can handle 53-bit unsigned integers continuously and exactly, 53-28 --> 25, any double with only 25 significant bits can be multiplied by n and still be exact.
Segment d into 2 doubles dmsb,dlsb, the 25-most significant digits and the 28- least.
int exp;
double dmsb = frexp(d, &exp); // exact result
dmsb = floor(dmsb * POW2_25); // exact result
dmsb /= POW2_25; // exact result
dmsb *= pow(2, exp); // exact result
double dlsb = d - dmsb; // exact result
Then each multiplication (or successive addition) of dmsb*n will be exact. (this is the important part.) dlsb*n will only error in its least few bits.
double next()
{
d_sum_msb += dmsb; // exact
d_sum_lsb += dlsb;
double angle = fmod(d_sum_msb, M_PI*2); // exact
angle += fmod(d_sum_lsb, M_PI*2);
return sin(angle);
}
Note: fmod(x,y) results are expected to be exact give exact x,y.
#include <stdio.h>
#include <math.h>
#define AS_n 158760000
double AS_d = 300000006.7846112 / AS_n;
double AS_d_sum_msb = 0.0;
double AS_d_sum_lsb = 0.0;
double AS_dmsb = 0.0;
double AS_dlsb = 0.0;
double next() {
AS_d_sum_msb += AS_dmsb; // exact
AS_d_sum_lsb += AS_dlsb;
double angle = fmod(AS_d_sum_msb, M_PI * 2); // exact
angle += fmod(AS_d_sum_lsb, M_PI * 2);
return sin(angle);
}
#define POW2_25 (1U << 25)
int main(void) {
int exp;
AS_dmsb = frexp(AS_d, &exp); // exact result
AS_dmsb = floor(AS_dmsb * POW2_25); // exact result
AS_dmsb /= POW2_25; // exact result
AS_dmsb *= pow(2, exp); // exact result
AS_dlsb = AS_d - AS_dmsb; // exact result
double y;
for (long i = 0; i < AS_n; i++)
y = next();
printf("%.20f\n", y);
}
Output
0.04630942695385031893
Use degrees
Recommend using degrees as 360 degrees is the exact period and M_PI*2 radians is an approximation. C cannot represent π exactly.
If OP still wants to use radians, for further insight on performing the mod of π, see Good to the Last Bit

Resources