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 double
s 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 double
s 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