Skip to main content

rustymath_core/
trig.rs

1//! Shared short trigonometric reductions and polynomials, with scalar repairs for rare large arguments. Polynomial sources: Arm Optimized Routines; see THIRD-PARTY.md.
2use crate::{Float, buffer::FloatWidth, facts::Repair, float::Native, recipe::{NativeOps, Ops}};
3use std::f64::consts::FRAC_1_PI;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
6pub(crate) enum Kind { Sin, Cos, Tan }
7
8impl Kind {
9    #[inline]
10    fn scale(self) -> f64 { if self == Self::Tan { 2.0 * FRAC_1_PI } else { FRAC_1_PI } }
11    #[inline]
12    fn offset(self) -> f64 { if self == Self::Cos { 0.5 } else { 0.0 } }
13    #[inline]
14    pub(crate) fn predicate<F: Float, O: Ops<F> + ?Sized>(self, o: &O, x: O::V) -> O::B {
15        let a = o.magnitude(x);
16        let limit = o.number(F::narrow(if o.width() == FloatWidth::F64 { 8388608.0 } else { 1048576.0 }));
17        o.and(o.less(limit, a), o.less(a, o.number(F::infinity())))
18    }
19    pub(crate) fn routine(self) -> Repair {
20        match self {
21            Self::Sin => Repair { single: sin_single, double: sin_double }, Self::Cos => Repair { single: cos_single, double: cos_double },
22            Self::Tan => Repair { single: tan_single, double: tan_double }
23        }
24    }
25}
26
27extern "C" fn sin_single(x: f32) -> f32 { repair::<{ crate::FUSED }>(x as f64, Kind::Sin) as f32 }
28extern "C" fn sin_double(x: f64) -> f64 { repair::<{ crate::FUSED }>(x, Kind::Sin) }
29extern "C" fn cos_single(x: f32) -> f32 { repair::<{ crate::FUSED }>(x as f64, Kind::Cos) as f32 }
30extern "C" fn cos_double(x: f64) -> f64 { repair::<{ crate::FUSED }>(x, Kind::Cos) }
31extern "C" fn tan_single(x: f32) -> f32 { repair::<{ crate::FUSED }>(x as f64, Kind::Tan) as f32 }
32extern "C" fn tan_double(x: f64) -> f64 { repair::<{ crate::FUSED }>(x, Kind::Tan) }
33
34#[derive(Clone, Copy, Debug)]
35pub struct Sin;
36#[derive(Clone, Copy, Debug)]
37pub struct Cos;
38#[derive(Clone, Copy, Debug)]
39pub struct Tan;
40
41#[inline]
42fn scalar<R: Native, const FUSED: bool>(x: R, kind: Kind) -> R {
43    let o = NativeOps::<FUSED>::DEFAULT;
44    if kind.predicate(&o, x) { R::narrow(repair::<FUSED>(x.wide(), kind)) } else { formula(&o, x, kind) }
45}
46#[inline]
47pub(crate) fn sin<R: Native, const FUSED: bool>(x: R) -> R { scalar::<R, FUSED>(x, Kind::Sin) }
48#[inline]
49pub(crate) fn cos<R: Native, const FUSED: bool>(x: R) -> R { scalar::<R, FUSED>(x, Kind::Cos) }
50#[inline]
51pub(crate) fn tan<R: Native, const FUSED: bool>(x: R) -> R { scalar::<R, FUSED>(x, Kind::Tan) }
52
53#[inline]
54pub(crate) fn formula<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, kind: Kind) -> O::V {
55    let zero = o.number(F::zero());
56    let finite = o.less(o.magnitude(x), o.number(F::infinity()));
57    let safe = o.select(o.and(finite, o.not(kind.predicate(o, x))), x, zero);
58    let (r, n) = if o.fused() { reduce_fused(o, safe, kind) } else { reduce_split(o, safe, kind) };
59    // n is a whole number well inside the shift's exact range, so rounding n/2 with the shift gives its parity exactly. An integer conversion would run scalar on AVX2.
60    let (half, shift) = (o.multiply(n, o.number(F::narrow(0.5))), crate::recipe::rounding_shift(o));
61    let even = o.subtract(o.add(half, shift), shift);
62    let odd = o.not(o.equal(n, o.add(even, even)));
63    let y = if kind == Kind::Tan { tangent(o, r, odd) } else {
64        let y = sine(o, r);
65        let y = o.select(odd, o.negate(y), y);
66        o.select(o.equal(x, zero), if kind == Kind::Cos { o.number(F::one()) } else { x }, y)
67    };
68    o.select(finite, y, o.number(F::nan()))
69}
70
71const PI_FUSED_64: [f64; 3] = [3.141592653589793, 1.224646799147353e-16, 2.165713347843828e-32];
72const PI_FUSED_32: [f64; 3] = [3.141592502593994, 1.5099578831723193e-7, 1.0780605906948477e-14];
73const PI_SPLIT_64: [f64; 4] = [3.141592651605606, 1.9841871583270443e-9, 1.03403659577805e-18, 5.807711947958721e-28];
74const PI_SPLIT_32: [f64; 2] = [3.141592651605606, 1.984187159361081e-9];
75
76#[inline]
77fn reduce_fused<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, kind: Kind) -> (O::V, O::V) {
78    let shift = crate::recipe::rounding_shift(o);
79    let z = if kind.offset() == 0.0 { o.multiply_add(x, o.number(F::narrow(kind.scale())), shift) }
80    else { o.add(o.multiply_add(x, o.number(F::narrow(kind.scale())), o.number(F::narrow(kind.offset()))), shift) };
81    let n = o.subtract(z, shift);
82    let m = if kind == Kind::Tan { o.multiply(n, o.number(F::narrow(0.5))) } else { o.subtract(n, o.number(F::narrow(kind.offset()))) };
83    let mut r = x;
84    for p in if o.width() == FloatWidth::F64 { PI_FUSED_64 } else { PI_FUSED_32 } { r = o.multiply_add(m, o.number(F::narrow(-p)), r); }
85    (r, n)
86}
87
88#[inline]
89fn reduce_split<F: Float, O: Ops<F> + ?Sized>(o: &O, x: O::V, kind: Kind) -> (O::V, O::V) {
90    let wide = o.widen(x);
91    let z = o.wide_multiply_add(wide, o.wide_number(kind.scale()), o.wide_number(kind.offset()));
92    let z = o.wide_multiply_add(z, o.wide_number(1.0), o.wide_number(6755399441055744.0));
93    let n = o.wide_multiply_add(z, o.wide_number(1.0), o.wide_number(-6755399441055744.0));
94    let m = if kind == Kind::Tan { o.wide_multiply_add(n, o.wide_number(0.5), o.wide_number(0.0)) }
95    else { o.wide_multiply_add(n, o.wide_number(1.0), o.wide_number(-kind.offset())) };
96    let mut r = wide;
97    let parts: &[f64] = if o.width() == FloatWidth::F64 { &PI_SPLIT_64 } else { &PI_SPLIT_32 };
98    for &p in parts { r = o.wide_multiply_add(m, o.wide_number(-p), r); }
99    (o.narrow(r), o.narrow(n))
100}
101
102const SIN_64: [f64; 7] = [
103    -7.364473665132213e-13, 1.604733546032205e-10, -2.505185490163253e-8, 2.755731618671281e-6, -0.0001984126982169595, 0.008333333333273485,
104    -0.1666666666666606,
105];
106const SIN_32: [f64; 4] = [2.6105958568223286e-6, -0.00019811994570773095, 0.00833310279995203, -0.16666661202907562];
107#[inline]
108fn sine<F: Float, O: Ops<F> + ?Sized>(o: &O, r: O::V) -> O::V {
109    let r2 = o.multiply(r, r);
110    let p = crate::recipe::horner(o, r2, if o.width() == FloatWidth::F64 { &SIN_64 } else { &SIN_32 });
111    o.multiply_add(o.multiply(r, r2), p, r)
112}
113
114const TAN_64: [f64; 9] = [
115    0.0003287231044337369, 0.000559125460707824, 0.0014615495400569908, 0.0035914987665774814, 0.008863277952340431, 0.021869486815122453, 0.05396825400765754,
116    0.13333333333289177, 0.33333333333333504,
117];
118const TAN_32: [f64; 4] = [0.025565408170223236, 0.053473446518182755, 0.13335898518562317, 0.33333292603492737];
119#[inline]
120fn tangent<F: Float, O: Ops<F> + ?Sized>(o: &O, r: O::V, odd: O::B) -> O::V {
121    let u = o.multiply(r, o.number(F::narrow(0.5)));
122    let u2 = o.multiply(u, u);
123    let p = crate::recipe::horner(o, u2, if o.width() == FloatWidth::F64 { &TAN_64 } else { &TAN_32 });
124    let t = o.multiply_add(o.multiply(u, u2), p, u);
125    let below = o.multiply_add(t, t, o.number(-F::one()));
126    let twice = o.add(t, t);
127    o.divide(o.select(odd, below, o.negate(twice)), o.select(odd, twice, below))
128}
129
130#[inline(never)]
131fn repair<const FUSED: bool>(x: f64, kind: Kind) -> f64 {
132    let (r, odd) = exact(x, kind);
133    let o = NativeOps::<FUSED>::DEFAULT;
134    let y = if kind == Kind::Tan { tangent(&o, r, odd) } else { sine(&o, r) };
135    let negate = if kind == Kind::Tan { x < 0.0 } else { odd ^ (kind == Kind::Sin && x < 0.0) };
136    if negate { -y } else { y }
137}
138/// 1/(2π), downwards, as 1280 bits after the binary point, most significant first.
139const INV_2PI: [u64; 20] = [
140    0x28be60db9391054a, 0x7f09d5f47d4d3770, 0x36d8a5664f10e410, 0x7f9458eaf7aef158, 0x6dc91b8e909374b8, 0x01924bba82746487, 0x3f877ac72c4a69cf,
141    0xba208d7d4baed121, 0x3a671c09ad17df90, 0x4e64758e60d4ce7d, 0x272117e2ef7e4a0e, 0xc7fe25fff7816603, 0xfbcbc462d6829b47, 0xdb4d9fb3c9f2c26d,
142    0xd3d18fd9a797fa8b, 0x5d49eeb1faf97c5e, 0xcf41ce7de294a4ba, 0x9afed7ec47e35742, 0x1580cc11bf1edaea, 0xfc33ef0826bd0d87,
143];
144/// π × 2^126, rounded.
145const PI_FIXED: u128 = 0xc90fdaa22168c234c4c6628b80dc1cd1;
146
147/// r and whether n is odd, for the function `kind` names at a finite x beyond `limit`, by Payne and Hanek's reduction of |x|. Integer arithmetic on the bits of 1/(2π) gives the fraction of |x|/(2π) to 128 bits, so even an x close to a multiple of π reduces accurately.
148fn exact(x: f64, kind: Kind) -> (f64, bool) {
149    let bits = x.abs().to_bits();
150    let (m, k) = ((bits & ((1 << 52) - 1)) | 1 << 52, (bits >> 52) as i64 - 1075);
151    // |x| = m × 2^k. The bits of 1/(2π) whose products with |x| are whole numbers drop out: the first k bits. The next 192 bits give the fraction to well beyond 128 bits.
152    let skip = k.max(0) as usize;
153    let word = |i: usize| ((((INV_2PI[i] as u128) << 64 | INV_2PI[i + 1] as u128) << (skip % 64)) >> 64) as u64;
154    let w = skip / 64;
155    let low = m as u128 * word(w + 2) as u128;
156    let mid = m as u128 * word(w + 1) as u128 + (low >> 64);
157    let high = m as u128 * word(w) as u128 + (mid >> 64);
158    // The product's bits from 2^64 up hold the fraction when k is at least 0, shifted up by -k bits when k is negative.
159    let below = (-k).max(0) as u32;
160    let fraction = (((high as u64 as u128) << 64 | mid as u64 as u128) >> below) | (high >> 64).checked_shl(128 - below).unwrap_or(0);
161    // As a fixed-point number with 127 bits after the point, the fraction is z = |x|/π, modulo 2. Tangent's period is π/2, so its z doubles. Adding h + ½ and keeping the bit above the point gives the parity of n, the nearest whole number to z + h. d = z + h - n lies from -½ to ½.
162    let z = if kind == Kind::Tan { fraction << 1 } else { fraction };
163    let t = z.wrapping_add(if kind == Kind::Cos { 1 << 126 } else { 0 }).wrapping_add(1 << 126);
164    let d = (t & ((1 << 127) - 1)) as i128 - (1 << 126);
165    // r is d periods, from the high half of |d| × π·2^126, which has 125 bits after the point. Tangent's period is half of π.
166    let (a, b) = (d.unsigned_abs(), PI_FIXED);
167    let product = (a >> 64) * (b >> 64) + ((a >> 64) * (b as u64 as u128) >> 64) + ((a as u64 as u128) * (b >> 64) >> 64);
168    let scale = if kind == Kind::Tan { 2f64.powi(-126) } else { 2f64.powi(-125) };
169    ((product as f64 * scale).copysign(d as f64), t >> 127 == 1)
170}