Coverage Report

Created: 2025-10-10 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.25/src/sincos.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 6/2025. All rights reserved.
3
 * //
4
 * // Redistribution and use in source and binary forms, with or without modification,
5
 * // are permitted provided that the following conditions are met:
6
 * //
7
 * // 1.  Redistributions of source code must retain the above copyright notice, this
8
 * // list of conditions and the following disclaimer.
9
 * //
10
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11
 * // this list of conditions and the following disclaimer in the documentation
12
 * // and/or other materials provided with the distribution.
13
 * //
14
 * // 3.  Neither the name of the copyright holder nor the names of its
15
 * // contributors may be used to endorse or promote products derived from
16
 * // this software without specific prior written permission.
17
 * //
18
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
use crate::common::*;
30
use crate::double_double::DoubleDouble;
31
use crate::sin::{
32
    cos_accurate_near_zero, range_reduction_small, sin_accurate_near_zero, sincos_eval,
33
};
34
use crate::sin_helper::sincos_eval_dd;
35
use crate::sin_table::SIN_K_PI_OVER_128;
36
use crate::sincos_reduce::LargeArgumentReduction;
37
use std::hint::black_box;
38
39
/// Sine and cosine for double precision
40
///
41
/// ULP 0.5
42
0
pub fn f_sincos(x: f64) -> (f64, f64) {
43
0
    let x_e = (x.to_bits() >> 52) & 0x7ff;
44
    const E_BIAS: u64 = (1u64 << (11 - 1u64)) - 1u64;
45
46
    let y: DoubleDouble;
47
    let k;
48
49
0
    let mut argument_reduction = LargeArgumentReduction::default();
50
51
    // |x| < 2^32 (with FMA) or |x| < 2^23 (w/o FMA)
52
0
    if x_e < E_BIAS + 16 {
53
        // |x| < 2^-26
54
0
        let ax = x.to_bits() & 0x7fff_ffff_ffff_ffff;
55
0
        if ax <= 0x3fa921fbd34a9550 {
56
            // |x| <= 0.0490874
57
0
            if x_e < E_BIAS - 27 {
58
                // Signed zeros.
59
0
                if x == 0.0 {
60
0
                    return (x, 1.0);
61
0
                }
62
                // For |x| < 2^-26, |sin(x) - x| < ulp(x)/2.
63
0
                let s_sin = dyad_fmla(x, f64::from_bits(0xbc90000000000000), x);
64
0
                let s_cos = black_box(1.0) - min_normal_f64();
65
0
                return (s_sin, s_cos);
66
0
            }
67
68
            // Polynomial for sin(x)/x
69
            // Generated by Sollya:
70
            // d = [0, 0.0490874];
71
            // f_sin = sin(y)/y;
72
            // Q = fpminimax(f_sin, [|0, 2, 4, 6, 8|], [|1, D...|], d, relative, floating);
73
            const SIN_C: [u64; 4] = [
74
                0xbfc5555555555555,
75
                0x3f8111111110e45a,
76
                0xbf2a019ffd7fdaaf,
77
                0x3ec71819b9bf01ef,
78
            ];
79
80
0
            let x2 = x * x;
81
0
            let x4 = x2 * x2;
82
83
0
            let p01 = f_fmla(x2, f64::from_bits(SIN_C[1]), f64::from_bits(SIN_C[0]));
84
0
            let p23 = f_fmla(x2, f64::from_bits(SIN_C[3]), f64::from_bits(SIN_C[2]));
85
0
            let w0 = f_fmla(x4, p23, p01);
86
0
            let w1 = x2 * w0 * x;
87
0
            let r_sin = DoubleDouble::from_exact_add(x, w1);
88
89
            // Polynomial for cos(x)
90
            // Generated by Sollya:
91
            // d = [0, 0.0490874];
92
            // f_cos = cos(y);
93
            // Q = fpminimax(f_cos, [|0, 2, 4, 6, 8|], [|1, D...|], d, relative, floating);
94
            const COS_C: [u64; 4] = [
95
                0xbfe0000000000000,
96
                0x3fa55555555554a4,
97
                0xbf56c16c1619b84a,
98
                0x3efa013d3d01cf7f,
99
            ];
100
0
            let p01 = f_fmla(x2, f64::from_bits(COS_C[1]), f64::from_bits(COS_C[0]));
101
0
            let p23 = f_fmla(x2, f64::from_bits(COS_C[3]), f64::from_bits(COS_C[2]));
102
0
            let w0 = f_fmla(x4, p23, p01);
103
0
            let w1 = x2 * w0;
104
0
            let r_cos = DoubleDouble::from_exact_add(1., w1);
105
106
0
            let err = f_fmla(
107
0
                x2,
108
0
                f64::from_bits(0x3cb0000000000000), // 2^-52
109
0
                f64::from_bits(0x3be0000000000000), // 2^-65
110
            );
111
112
0
            let sin_ub = r_sin.hi + (r_sin.lo + err);
113
0
            let sin_lb = r_sin.hi + (r_sin.lo - err);
114
0
            let sin_x = if sin_ub == sin_lb {
115
0
                sin_ub
116
            } else {
117
0
                sin_accurate_near_zero(x)
118
            };
119
120
0
            let cos_ub = r_cos.hi + (r_cos.lo + err);
121
0
            let cos_lb = r_cos.hi + (r_cos.lo - err);
122
0
            let cos_x = if cos_ub == cos_lb {
123
0
                cos_ub
124
            } else {
125
0
                cos_accurate_near_zero(x)
126
            };
127
0
            return (sin_x, cos_x);
128
0
        } else {
129
0
            // // Small range reduction.
130
0
            (y, k) = range_reduction_small(x);
131
0
        }
132
    } else {
133
        // Inf or NaN
134
0
        if x_e > 2 * E_BIAS {
135
            // sin(+-Inf) = NaN
136
0
            return (x + f64::NAN, x + f64::NAN);
137
0
        }
138
139
        // Large range reduction.
140
0
        (k, y) = argument_reduction.reduce(x);
141
    }
142
143
0
    let r_sincos = sincos_eval(y);
144
0
    let (sin_y, cos_y) = (r_sincos.v_sin, r_sincos.v_cos);
145
146
    // Fast look up version, but needs 256-entry table.
147
    // cos(k * pi/128) = sin(k * pi/128 + pi/2) = sin((k + 64) * pi/128).
148
0
    let sk = SIN_K_PI_OVER_128[(k & 255) as usize];
149
0
    let ck = SIN_K_PI_OVER_128[((k.wrapping_add(64)) & 255) as usize];
150
0
    let sin_k = DoubleDouble::from_bit_pair(sk);
151
0
    let cos_k = DoubleDouble::from_bit_pair(ck);
152
153
0
    let msin_k = -sin_k;
154
155
    // After range reduction, k = round(x * 128 / pi) and y = x - k * (pi / 128).
156
    // So k is an integer and -pi / 256 <= y <= pi / 256.
157
    // Then sin(x) = sin((k * pi/128 + y)
158
    //             = sin(y) * cos(k*pi/128) + cos(y) * sin(k*pi/128)
159
0
    let sin_k_cos_y = DoubleDouble::quick_mult(cos_y, sin_k);
160
0
    let cos_k_sin_y = DoubleDouble::quick_mult(sin_y, cos_k);
161
    //      cos(x) = cos((k * pi/128 + y)
162
    //             = cos(y) * cos(k*pi/128) - sin(y) * sin(k*pi/128)
163
0
    let cos_k_cos_y = DoubleDouble::quick_mult(cos_y, cos_k);
164
0
    let msin_k_sin_y = DoubleDouble::quick_mult(sin_y, msin_k);
165
166
    // cos_k_sin_y is always >> sin_k_cos_y
167
0
    let mut sin_dd = DoubleDouble::from_exact_add(sin_k_cos_y.hi, cos_k_sin_y.hi);
168
    // cos_k_cos_y is always >> msin_k_sin_y
169
0
    let mut cos_dd = DoubleDouble::from_exact_add(cos_k_cos_y.hi, msin_k_sin_y.hi);
170
0
    sin_dd.lo += sin_k_cos_y.lo + cos_k_sin_y.lo;
171
0
    cos_dd.lo += msin_k_sin_y.lo + cos_k_cos_y.lo;
172
173
0
    let sin_lp = sin_dd.lo + r_sincos.err;
174
0
    let sin_lm = sin_dd.lo - r_sincos.err;
175
0
    let cos_lp = cos_dd.lo + r_sincos.err;
176
0
    let cos_lm = cos_dd.lo - r_sincos.err;
177
178
0
    let sin_upper = sin_dd.hi + sin_lp;
179
0
    let sin_lower = sin_dd.hi + sin_lm;
180
0
    let cos_upper = cos_dd.hi + cos_lp;
181
0
    let cos_lower = cos_dd.hi + cos_lm;
182
183
    // Ziv's rounding test.
184
0
    if sin_upper == sin_lower && cos_upper == cos_lower {
185
0
        return (sin_upper, cos_upper);
186
0
    }
187
188
0
    sincos_hard(y, sin_k, cos_k, sin_upper, sin_lower, cos_upper, cos_lower)
189
0
}
190
191
#[cold]
192
#[inline(never)]
193
#[allow(clippy::too_many_arguments)]
194
0
fn sincos_hard(
195
0
    y: DoubleDouble,
196
0
    sin_k: DoubleDouble,
197
0
    cos_k: DoubleDouble,
198
0
    sin_upper: f64,
199
0
    sin_lower: f64,
200
0
    cos_upper: f64,
201
0
    cos_lower: f64,
202
0
) -> (f64, f64) {
203
0
    let r_sincos = sincos_eval_dd(y);
204
205
0
    let msin_k = -sin_k;
206
207
0
    let sin_x = if sin_upper == sin_lower {
208
0
        sin_upper
209
    } else {
210
        // sin(x) = sin((k * pi/128 + u)
211
        //        = sin(u) * cos(k*pi/128) + cos(u) * sin(k*pi/128)
212
213
0
        DoubleDouble::mul_add(sin_k, r_sincos.v_cos, cos_k * r_sincos.v_sin).to_f64()
214
    };
215
216
0
    let cos_x = if cos_upper == cos_lower {
217
0
        cos_upper
218
    } else {
219
        // cos(x) = cos((k * pi/128 + u)
220
        //        = cos(u) * cos(k*pi/128) - sin(u) * sin(k*pi/128)
221
0
        DoubleDouble::mul_add(cos_k, r_sincos.v_cos, msin_k * r_sincos.v_sin).to_f64()
222
    };
223
0
    (sin_x, cos_x)
224
0
}
225
226
#[cfg(test)]
227
mod tests {
228
    use super::*;
229
230
    #[test]
231
    fn f_sincos_test() {
232
        let subnormal = f_sincos(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015708065354637772);
233
        assert_eq!(subnormal.0, 1.5708065354637772e-307);
234
        assert_eq!(subnormal.1, 1.0);
235
        let zx_0 = f_sincos(0.0);
236
        assert_eq!(zx_0.0, 0.0);
237
        assert_eq!(zx_0.1, 1.0);
238
        let zx_1 = f_sincos(1.0);
239
        assert_eq!(zx_1.0, 0.8414709848078965);
240
        assert_eq!(zx_1.1, 0.5403023058681398);
241
        let zx_0_p5 = f_sincos(-0.5);
242
        assert_eq!(zx_0_p5.0, -0.479425538604203);
243
        assert_eq!(zx_0_p5.1, 0.8775825618903728);
244
245
        let zx_1 = f_sincos(0.002341235432);
246
        assert_eq!(zx_1.0, 0.0023412332931324344);
247
        assert_eq!(zx_1.1, 0.9999972593095778);
248
249
        let zx_1 = f_sincos(0.0198676543432);
250
        assert_eq!(zx_1.0, 0.019866347330026367);
251
        assert_eq!(zx_1.1, 0.9998026446473137);
252
    }
253
}