#include <math.h>
#include "arb.h"
#define ONE_OVER_PI 0.31830988618379067154
#define PI 3.1415926535897932385
#define EPSILON ldexp(1.0, -30)
#define MAX_EXP 20
static const double sin_tab[] = {
0.0,0.062459317842380198585,0.12467473338522768996,
0.18640329676226988455,0.24740395925452292960,0.30743851458038085067,
0.36627252908604756137,0.42367625720393801036,0.47942553860420300027,
0.53330267353602017333,0.58509727294046215481,0.63460708001526929685,
0.68163876002333416673,0.72600865526071254966,0.76754350223602703963,
0.80608110826069299518,0.84147098480789650665,0.87357493516707112023,
0.90226759409909516292,0.92743691738486767172,0.94898461935558621435,
0.96682655669618022997,0.98089305702315569609,0.99112919095376166988,
0.99749498660405443094,0.99996558567824887530,
};
static const double cos_tab[] = {
1.0,0.99804751070009914963,0.99219766722932905315,
0.98247331310125525749,0.96891242171064478414,0.95156794804817220215,
0.93050762191231429115,0.90581368342593642074,0.87758256189037271612,
0.84592449923106795446,0.81096311950521790219,0.77283494615247154481,
0.73168886887382088631,0.68768556222050484451,0.64099685816332513036,
0.59180507509247750546,0.54030230586813971740,0.48668966770196333087,
0.43117651679866617655,0.37397963082453319229,0.31532236239526866545,
0.25543376688881169791,0.19454770798898718445,0.13290194445282520566,
0.070737201667702910088,0.0082962316238583774779,
};
static void
sin_cos(double * sin_a, double * cos_a, int * q, double a)
{
double as, ac, t, b, bs, bc, v;
int i, qa;
*q = qa = floor(a * (2.0 * ONE_OVER_PI));
a = a - qa * (0.5 * PI);
if (a < 0.0)
a = 0.0;
if (a > 0.5 * PI)
a = 0.5 * PI;
i = a * 16.0;
if (i < 0 || i > 25)
flint_throw(FLINT_ERROR, "(%s)\n", __func__);
as = sin_tab[i];
ac = cos_tab[i];
b = a - i * (1 / 16.0);
v = b * b;
bs = 2.7557319223985890653e-6 * v;
bs = (-0.0001984126984126984127 + bs) * v;
bs = (0.0083333333333333333333 + bs) * v;
bs = (-0.16666666666666666667 + bs) * v;
bs = (1.0 + bs) * b;
bc = -2.7557319223985890653e-7 * v;
bc = (0.000024801587301587301587 + bc) * v;
bc = (-0.0013888888888888888889 + bc) * v;
bc = (0.041666666666666666667 + bc) * v;
bc = (-0.5 + bc) * v;
bc = 1.0 + bc;
t = as * bc + ac * bs;
ac = ac * bc - as * bs;
as = t;
if ((qa & 3) == 0)
{
*sin_a = as;
*cos_a = ac;
}
else if ((qa & 3) == 1)
{
*sin_a = ac;
*cos_a = -as;
}
else if ((qa & 3) == 2)
{
*sin_a = -as;
*cos_a = -ac;
}
else
{
*sin_a = -ac;
*cos_a = as;
}
}
static void
_arb_mod_2pi(arb_t x, slong mag)
{
arb_t t;
arf_t q;
arf_init(q);
arb_init(t);
arb_const_pi(t, mag + 53);
arb_mul_2exp_si(t, t, 1);
arf_div(q, arb_midref(x), arb_midref(t), mag + 10, ARF_RND_NEAR);
arf_floor(q, q);
arb_submul_arf(x, t, q, 53);
arf_clear(q);
arb_clear(t);
}
void
_arb_sin_cos_wide(arb_t sinx, arb_t cosx, const arf_t xmid, const mag_t xrad, slong prec)
{
double m, a, b, r, cos_min, cos_max, sin_min, sin_max;
double as, ac, bs, bc;
int i, qa, qb;
slong mag;
mag = arf_abs_bound_lt_2exp_si(xmid);
if (mag > FLINT_MAX(65536, 4 * prec) || mag_cmp_2exp_si(xrad, 3) >= 0)
{
if (sinx != NULL)
arb_zero_pm_one(sinx);
if (cosx != NULL)
arb_zero_pm_one(cosx);
return;
}
else if (mag <= MAX_EXP)
{
m = arf_get_d(xmid, ARF_RND_DOWN);
r = mag_get_d(xrad);
}
else
{
arb_t t;
arb_init(t);
arf_set(arb_midref(t), xmid);
mag_set(arb_radref(t), xrad);
_arb_mod_2pi(t, mag);
if (arf_cmpabs_2exp_si(arb_midref(t), 5) > 0 ||
mag_cmp_2exp_si(arb_radref(t), 5) > 0)
{
flint_printf("unexpected precision loss in sin_cos_wide\n");
if (sinx != NULL)
arb_zero_pm_one(sinx);
if (cosx != NULL)
arb_zero_pm_one(cosx);
arb_clear(t);
return;
}
m = arf_get_d(arb_midref(t), ARF_RND_DOWN);
r = mag_get_d(arb_radref(t));
arb_clear(t);
}
a = m - r;
b = m + r;
sin_cos(&as, &ac, &qa, a);
sin_cos(&bs, &bc, &qb, b);
sin_min = FLINT_MIN(as, bs);
sin_max = FLINT_MAX(as, bs);
cos_min = FLINT_MIN(ac, bc);
cos_max = FLINT_MAX(ac, bc);
for (i = qa; i < qb; i++)
{
if ((i & 3) == 1) cos_min = -1.0;
if ((i & 3) == 3) cos_max = 1.0;
if ((i & 3) == 2) sin_min = -1.0;
if ((i & 3) == 0) sin_max = 1.0;
}
if (sinx != NULL)
{
a = (sin_max + sin_min) * 0.5;
r = (sin_max - sin_min) * 0.5 + EPSILON;
arf_set_d(arb_midref(sinx), a);
mag_set_d(arb_radref(sinx), r);
arb_set_round(sinx, sinx, prec);
}
if (cosx != NULL)
{
a = (cos_max + cos_min) * 0.5;
r = (cos_max - cos_min) * 0.5 + EPSILON;
arf_set_d(arb_midref(cosx), a);
mag_set_d(arb_radref(cosx), r);
arb_set_round(cosx, cosx, prec);
}
}
void
arb_sin_cos_wide(arb_t sinx, arb_t cosx, const arb_t x, slong prec)
{
_arb_sin_cos_wide(sinx, cosx, arb_midref(x), arb_radref(x), prec);
}