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))
}
fn reduce_pi(p: i64, q: i64) -> (i64, i128, i128) {
let (p, q) = (p as i128, q as i128);
let p = p.rem_euclid(2 * q);
let n = (4 * p + q).div_euclid(2 * q);
(n as i64, 2 * p - n * q, 2 * q)
}
fn f64_exact(x: i128) -> bool {
x.unsigned_abs() <= 1 << 53
}
fn split_int(x: i128) -> (f64, f64) {
let hi = x as f64;
(hi, (x - hi as i128) as f64)
}
fn remainder_dd(num: i128, q2: i128) -> DD {
if f64_exact(num) && f64_exact(q2) {
return PI.mul_f(num as f64) / DD::from_f64(q2 as f64);
}
let dd = |x: i128| {
let (hi, lo) = split_int(x);
DD { hi, lo }
};
PI * dd(num) / dd(q2)
}
pub fn sin_cos_pi(p: i64, q: i64) -> (DD, DD) {
assert!(q > 0, "sin_cos_pi: q = {q}");
let (n, num, q2) = reduce_pi(p, q);
quadrant(n, sin_cos_series(remainder_dd(num, q2)))
}
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, num, q2) = reduce_pi(p, q);
let r = if f64_exact(num) && f64_exact(q2) {
PI_OD.mul_f(num as f64) / OD::from_f64(q2 as f64)
} else {
let od = |x: i128| {
let (hi, lo) = split_int(x);
OD::new([hi, lo, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
};
PI_OD * od(num) / od(q2)
};
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 off = 2 * m as i64 - 1;
let tab: Vec<DD> = (-off..m as i64)
.map(|d| if d == 0 { DD::ZERO } else { d_full(d, n) })
.collect();
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 + tab[(i as i64 - jj as i64 + off) as usize].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_reduces_large_p() {
for q in [7i64, 40, 1_000_000_000_000_000] {
for p in [i64::MAX, i64::MAX - 5, i64::MIN, i64::MIN + 3] {
let pr = (p as i128).rem_euclid(2 * q as i128) as i64;
assert_eq!(sin_cos_pi_od(p, q), sin_cos_pi_od(pr, q), "{p}/{q}");
assert_eq!(sin_cos_pi(p, q), sin_cos_pi(pr, q), "{p}/{q}");
}
}
}
#[test]
fn sin_cos_pi_reduction_control_half_period_differs() {
let (p, q) = (i64::MAX - 5, 40);
let pr = (p as i128).rem_euclid(2 * q as i128) as i64;
let (s, c) = sin_cos_pi_od(pr + q, q);
assert_ne!(sin_cos_pi_od(p, q), (s, c));
assert_eq!(sin_cos_pi_od(p, q), (-s, -c));
let (s, c) = sin_cos_pi(pr + q, q);
assert_ne!(sin_cos_pi(p, q), (s, c));
assert_eq!(sin_cos_pi(p, q), (-s, -c));
}
fn small_angle(x: OD) -> (OD, OD) {
let (mut s, mut c) = (OD::from_f64(0.0), OD::from_f64(0.0));
let mut t = OD::from_f64(1.0);
for k in 0..22 {
if k % 2 == 0 {
c = c + if k % 4 == 0 { t } else { -t };
} else {
s = s + if k % 4 == 1 { t } else { -t };
}
t = t * x / OD::from_f64((k + 1) as f64);
}
(s, c)
}
const LARGE_Q: [i64; 3] = [(1 << 53) + 1, 1 << 62, i64::MAX];
fn large_q_error(f: impl Fn(i64, i64) -> (OD, OD)) -> [f64; 3] {
LARGE_Q.map(|q| {
let mut worst = 0.0f64;
for p in [1i64, 3, -5] {
let qd = OD::new([
q as f64,
(q as i128 - (q as f64) as i128) as f64,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
]);
let x = PI_OD.mul_f(p as f64) / qd;
let (s0, c0) = small_angle(x);
let (s, c) = f(p, q);
worst = worst.max((od_diff(s, s0) / s0.to_f64().abs()).max(od_diff(c, c0)));
}
worst
})
}
#[test]
fn sin_cos_pi_takes_large_denominators() {
let eo = large_q_error(sin_cos_pi_od);
let ed = large_q_error(|p, q| {
let (s, c) = sin_cos_pi(p, q);
(OD::from_dd(s), OD::from_dd(c))
});
println!("large q: od {eo:?}, dd {ed:?}");
assert!(
eo.iter().all(|&e| e < 1e-125) && ed.iter().all(|&e| e < 1e-31),
"{eo:?} {ed:?}"
);
}
#[test]
fn sin_cos_pi_large_q_control_former_remainder_fails() {
let former = |p: i64, q: i64| {
let (n, num, _) = reduce_pi(p, q);
let r = PI_OD.mul_f(num as f64) / OD::from_f64(q.wrapping_mul(2) 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),
}
};
let eo = large_q_error(former);
let ed = large_q_error(|p, q| {
let (n, num, _) = reduce_pi(p, q);
let r = PI.mul_f(num as f64) / DD::from_f64(q.wrapping_mul(2) as f64);
let (s, c) = quadrant(n, sin_cos_series(r));
(OD::from_dd(s), OD::from_dd(c))
});
println!("control, former remainder: od {eo:?}, dd {ed:?}");
assert!(
eo.iter().all(|&e| e >= 1e-125) && ed.iter().all(|&e| e >= 1e-31),
"control did not fire: {eo:?} {ed:?}"
);
}
#[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}");
}
fn direct(parity: Parity, m: usize, i: usize, j: usize) -> DD {
let n = period_points(parity, m);
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)],
};
images
.iter()
.filter(|&&(jj, _)| jj != i)
.fold(DD::ZERO, |v, &(jj, sgn)| v + d_full(i as i64 - jj as i64, n).mul_f(sgn))
}
fn differing<const B: usize>(transposed: bool) -> usize {
let m = 8 * B;
let mut n = 0;
for parity in [Parity::Odd, Parity::Even, Parity::Full] {
let d = dmat::<B>(parity);
for i in 0..m {
for j in 0..m {
let (a, b) = (
d.cols[j].get(i),
if transposed {
direct(parity, m, j, i)
} else {
direct(parity, m, i, j)
},
);
n += usize::from(a.hi.to_bits() != b.hi.to_bits() || a.lo.to_bits() != b.lo.to_bits());
}
}
}
n
}
#[test]
fn dmat_equals_direct_sums() {
assert_eq!(
(differing::<1>(false), differing::<2>(false), differing::<4>(false)),
(0, 0, 0)
);
}
#[test]
fn dmat_control_transposed_differs() {
assert!(differing::<1>(true) > 0 && differing::<2>(true) > 0 && differing::<4>(true) > 0);
}
}