use crate::lane::DMat;
use multidouble::dd::DD;
use multidouble::od::OD;
pub const PI: DD = DD {
hi: std::f64::consts::PI,
lo: 1.2246467991473532e-16,
};
const PI_2: DD = DD {
hi: std::f64::consts::FRAC_PI_2,
lo: 6.123233995736766e-17,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Parity {
Odd,
Even,
Full,
}
fn sin_cos_series(r: DD) -> (DD, DD) {
let r2 = r * r;
let (mut s, mut c) = (r, DD::ONE);
let (mut ts, mut tc) = (r, DD::ONE);
for k in 1..30 {
let kf = k as f64;
ts = -(ts * r2) / DD::from_f64((2.0 * kf) * (2.0 * kf + 1.0));
tc = -(tc * r2) / DD::from_f64((2.0 * kf - 1.0) * (2.0 * kf));
s = s + ts;
c = c + tc;
if tc.hi.abs() < 2e-35 && ts.hi.abs() < 2e-35 * r.hi.abs().max(1e-300) {
break;
}
}
(s, c)
}
fn quadrant(n: i64, (s, c): (DD, DD)) -> (DD, DD) {
match n.rem_euclid(4) {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
pub fn sin_cos(x: DD) -> (DD, DD) {
let n = (x.hi / std::f64::consts::FRAC_PI_2).round();
let r = x - PI_2.mul_f(n);
quadrant(n as i64, sin_cos_series(r))
}
pub fn sin_cos_pi(p: i64, q: i64) -> (DD, DD) {
assert!(q > 0, "sin_cos_pi: q = {q}");
let n = (4 * p + q).div_euclid(2 * q);
let num = 2 * p - n * q;
let r = PI.mul_f(num as f64) / DD::from_f64((2 * q) as f64);
quadrant(n, sin_cos_series(r))
}
pub const PI_OD: OD = OD {
x: [
std::f64::consts::PI,
1.2246467991473532e-16,
-2.9947698097183397e-33,
1.1124542208633653e-49,
5.672231979640316e-66,
1.7449862161352486e-83,
6.02937273224954e-100,
1.91012354687999e-116,
],
};
fn sin_cos_series_od(r: OD, tol: f64) -> (OD, OD) {
let r2 = r * r;
let (mut s, mut c) = (r, OD::from_f64(1.0));
let (mut ts, mut tc) = (r, OD::from_f64(1.0));
for k in 1..80 {
let kf = k as f64;
ts = -(ts * r2) / OD::from_f64((2.0 * kf) * (2.0 * kf + 1.0));
tc = -(tc * r2) / OD::from_f64((2.0 * kf - 1.0) * (2.0 * kf));
s = s + ts;
c = c + tc;
if tc.to_f64().abs() < tol && ts.to_f64().abs() < tol {
break;
}
}
(s, c)
}
pub fn sin_cos_pi_od(p: i64, q: i64) -> (OD, OD) {
assert!(q > 0, "sin_cos_pi_od: q = {q}");
let n = (4 * p + q).div_euclid(2 * q);
let num = 2 * p - n * q;
let r = PI_OD.mul_f(num as f64) / OD::from_f64((2 * q) as f64);
let (s, c) = sin_cos_series_od(r, 1e-136);
match n.rem_euclid(4) {
0 => (s, c),
1 => (c, -s),
2 => (-s, -c),
_ => (-c, s),
}
}
fn period_points(parity: Parity, m: usize) -> i64 {
match parity {
Parity::Full => m as i64,
_ => 2 * m as i64,
}
}
pub fn theta(parity: Parity, m: usize, j: usize) -> DD {
PI.mul_f(2.0 * j as f64) / DD::from_f64(period_points(parity, m) as f64)
}
pub fn sin_cos_grid(parity: Parity, m: usize, j: usize, k: i64) -> (DD, DD) {
sin_cos_pi(2 * k * j as i64, period_points(parity, m))
}
fn d_full(d: i64, n: i64) -> DD {
let (s, c) = sin_cos_pi(d, n);
let v = (c / s).mul_f(0.5);
if d.rem_euclid(2) == 0 {
v
} else {
-v
}
}
pub fn dmat<const B: usize>(parity: Parity) -> DMat<B> {
let m = 8 * B;
let n = period_points(parity, m);
let mut hi = vec![0.0; m * m];
let mut lo = vec![0.0; m * m];
for i in 0..m {
for j in 0..m {
let mut v = DD::ZERO;
let images: &[(usize, f64)] = match parity {
Parity::Full => &[(j, 1.0)],
Parity::Odd => &[(j, 1.0), (j + m, -1.0)],
Parity::Even => &[(j, 1.0), (j + m, 1.0)],
};
for &(jj, sgn) in images {
if jj != i {
v = v + d_full(i as i64 - jj as i64, n).mul_f(sgn);
}
}
hi[i * m + j] = v.hi;
lo[i * m + j] = v.lo;
}
}
DMat::<B>::from_rows(&hi, &lo)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lane::DV;
use crate::prec::nan_max;
use multidouble::field::Fl;
#[test]
fn sin_cos_agree() {
for p in -30..30 {
let x = PI.mul_f(p as f64) / DD::from_f64(7.0);
let (s, c) = sin_cos(x);
let (s2, c2) = sin_cos_pi(p, 7);
assert!((s - s2).hi.abs() < 1e-30 && (c - c2).hi.abs() < 1e-30, "p {p}");
assert!((s.hi - x.hi.sin()).abs() < 1e-15 && (c.hi - x.hi.cos()).abs() < 1e-15);
assert!((s * s + c * c - DD::ONE).hi.abs() < 1e-31);
}
}
#[test]
fn dmat_differentiates_harmonics() {
const B: usize = 2;
let m = 8 * B;
for (parity, ks) in [
(Parity::Odd, vec![1i64, 3, 5, 15]),
(Parity::Even, vec![0i64, 2, 8, 14]),
(Parity::Full, vec![0i64, 1, 4, 7]),
] {
let d = dmat::<B>(parity);
for &k in &ks {
let samples = |f: &dyn Fn(usize) -> DD| {
let v: Vec<DD> = (0..m).map(f).collect();
let (h, l) = multidouble::dd::split(&v);
Fl::<DV, B>::load(&h, &l)
};
let s = samples(&|j| sin_cos_grid(parity, m, j, k).0);
let c = samples(&|j| sin_cos_grid(parity, m, j, k).1);
let ds = d.apply(&s);
let dc = d.apply(&c);
for j in 0..m {
let kf = k as f64;
let e1 = (ds.get(j) - c.get(j).mul_f(kf)).hi.abs();
let e2 = (dc.get(j) + s.get(j).mul_f(kf)).hi.abs();
assert!(e1 < 1e-29 && e2 < 1e-29, "{parity:?} k {k} j {j}: {e1:e} {e2:e}");
}
}
}
}
fn od_diff(a: OD, b: OD) -> f64 {
(a - b).to_f64().abs()
}
fn atan_inv(n: f64) -> OD {
let x = OD::from_f64(1.0) / OD::from_f64(n);
let x2 = x * x;
let (mut s, mut p) = (x, x);
for k in 1..200 {
p = -(p * x2);
let t = p / OD::from_f64((2 * k + 1) as f64);
s = s + t;
if t.to_f64().abs() < 1e-140 {
break;
}
}
s
}
#[test]
fn pi_od_is_pi() {
let parsed = OD::parse(
"3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940813",
)
.unwrap();
assert_eq!(parsed.x, PI_OD.x);
let machin = atan_inv(5.0).mul_f(16.0) - atan_inv(239.0).mul_f(4.0);
let e = od_diff(machin, PI_OD);
println!("|Machin - PI_OD| = {e:.3e}");
assert!(e < 1e-125, "{e:e}");
}
#[test]
fn pi_od_control_quad_parts_fail() {
let mut q = PI_OD;
q.x[4..].iter_mut().for_each(|v| *v = 0.0);
let machin = atan_inv(5.0).mul_f(16.0) - atan_inv(239.0).mul_f(4.0);
assert!(od_diff(machin, q) > 1e-125);
}
#[test]
fn sin_cos_pi_od_agrees() {
let mut worst = 0.0f64;
for q in [7i64, 16, 40] {
for p in -3 * q..3 * q {
let (s, c) = sin_cos_pi_od(p, q);
let (sd, cd) = sin_cos_pi(p, q);
assert!(
(s.to_dd() - sd).hi.abs() < 1e-31 && (c.to_dd() - cd).hi.abs() < 1e-31,
"{p}/{q}"
);
let one = od_diff(s * s + c * c, OD::from_f64(1.0));
let (s2, _) = sin_cos_pi_od(2 * p, q);
let dbl = od_diff((s * c).mul_f(2.0), s2);
worst = worst.max(one).max(dbl);
assert!(one < 1e-126 && dbl < 1e-125, "{p}/{q}: {one:e} {dbl:e}");
}
}
println!("sin_cos_pi_od: worst identity error {worst:.3e}");
}
#[test]
fn sin_cos_pi_od_control_short_series_fails() {
let worst = (1..40)
.map(|p| {
let (s, c) = sin_cos_series_od(PI_OD.mul_f(p as f64) / OD::from_f64(160.0), 1e-90);
od_diff(s * s + c * c, OD::from_f64(1.0))
})
.fold(0.0f64, f64::max);
println!("control, short series: sin^2 + cos^2 - 1 up to {worst:.3e}");
assert!(worst > 1e-126, "{worst:e}");
}
#[test]
fn dmat_control_wrong_parity_fails() {
const B: usize = 1;
let m = 8 * B;
let d = dmat::<B>(Parity::Odd);
let v: Vec<DD> = (0..m).map(|j| sin_cos_grid(Parity::Odd, m, j, 2).1).collect();
let (h, l) = multidouble::dd::split(&v);
let c = Fl::<DV, B>::load(&h, &l);
let s: Vec<DD> = (0..m).map(|j| sin_cos_grid(Parity::Odd, m, j, 2).0).collect();
let dc = d.apply(&c);
let err = (0..m)
.map(|j| (dc.get(j) + s[j].mul_f(2.0)).hi.abs())
.fold(0.0, nan_max);
assert!(err > 1e-3, "{err:e}");
}
}