Coverage Report

Created: 2025-12-20 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.27/src/tangent/tan.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::bits::EXP_MASK;
30
use crate::common::{dyad_fmla, f_fmla};
31
use crate::double_double::DoubleDouble;
32
use crate::sin::range_reduction_small;
33
use crate::sincos_reduce::LargeArgumentReduction;
34
use crate::tangent::tanpi_table::TAN_K_PI_OVER_128;
35
36
#[inline]
37
0
pub(crate) fn tan_eval(u: DoubleDouble) -> (DoubleDouble, f64) {
38
    // Evaluate tan(y) = tan(x - k * (pi/128))
39
    // We use the degree-9 Taylor approximation:
40
    //   tan(y) ~ P(y) = y + y^3/3 + 2*y^5/15 + 17*y^7/315 + 62*y^9/2835
41
    // Then the error is bounded by:
42
    //   |tan(y) - P(y)| < 2^-6 * |y|^11 < 2^-6 * 2^-66 = 2^-72.
43
    // For y ~ u_hi + u_lo, fully expanding the polynomial and drop any terms
44
    // < ulp(u_hi^3) gives us:
45
    //   P(y) = y + y^3/3 + 2*y^5/15 + 17*y^7/315 + 62*y^9/2835 = ...
46
    // ~ u_hi + u_hi^3 * (1/3 + u_hi^2 * (2/15 + u_hi^2 * (17/315 +
47
    //                                                     + u_hi^2 * 62/2835))) +
48
    //        + u_lo (1 + u_hi^2 * (1 + u_hi^2 * 2/3))
49
0
    let u_hi_sq = u.hi * u.hi; // Error < ulp(u_hi^2) < 2^(-6 - 52) = 2^-58.
50
    // p1 ~ 17/315 + u_hi^2 62 / 2835.
51
0
    let p1 = f_fmla(
52
0
        u_hi_sq,
53
0
        f64::from_bits(0x3f9664f4882c10fa),
54
0
        f64::from_bits(0x3faba1ba1ba1ba1c),
55
    );
56
    // p2 ~ 1/3 + u_hi^2 2 / 15.
57
0
    let p2 = f_fmla(
58
0
        u_hi_sq,
59
0
        f64::from_bits(0x3fc1111111111111),
60
0
        f64::from_bits(0x3fd5555555555555),
61
    );
62
    // q1 ~ 1 + u_hi^2 * 2/3.
63
0
    let q1 = f_fmla(u_hi_sq, f64::from_bits(0x3fe5555555555555), 1.0);
64
0
    let u_hi_3 = u_hi_sq * u.hi;
65
0
    let u_hi_4 = u_hi_sq * u_hi_sq;
66
    // p3 ~ 1/3 + u_hi^2 * (2/15 + u_hi^2 * (17/315 + u_hi^2 * 62/2835))
67
0
    let p3 = f_fmla(u_hi_4, p1, p2);
68
    // q2 ~ 1 + u_hi^2 * (1 + u_hi^2 * 2/3)
69
0
    let q2 = f_fmla(u_hi_sq, q1, 1.0);
70
0
    let tan_lo = f_fmla(u_hi_3, p3, u.lo * q2);
71
    // Overall, |tan(y) - (u_hi + tan_lo)| < ulp(u_hi^3) <= 2^-71.
72
    // And the relative errors is:
73
    // |(tan(y) - (u_hi + tan_lo)) / tan(y) | <= 2*ulp(u_hi^2) < 2^-64
74
0
    let err = f_fmla(
75
0
        u_hi_3.abs(),
76
0
        f64::from_bits(0x3cc0000000000000),
77
0
        f64::from_bits(0x3990000000000000),
78
    );
79
0
    (DoubleDouble::from_exact_add(u.hi, tan_lo), err)
80
0
}
81
82
#[inline]
83
0
pub(crate) fn tan_eval_dd(x: DoubleDouble) -> DoubleDouble {
84
    // Generated by Sollya:
85
    // d = [0, pi/128];
86
    // f_tan = tan(x)/x;
87
    // Q = fpminimax(f_tan, [|0, 2, 4, 6, 8, 10, 12|], [|107...|], d);
88
    const C: [(u64, u64); 7] = [
89
        (0x0000000000000000, 0x3ff0000000000000),
90
        (0x3c75555549d35a4b, 0x3fd5555555555555),
91
        (0x3c413dd6ea580288, 0x3fc1111111111111),
92
        (0x3c4e100b946f0c89, 0x3faba1ba1ba1b9fe),
93
        (0xbc3c180dfac2b8c3, 0x3f9664f488307189),
94
        (0x3c1fd691c256a14a, 0x3f8226e300c1988e),
95
        (0x3bec7b08c90fdfc0, 0x3f6d739baeacd204),
96
    ];
97
0
    let x2 = DoubleDouble::quick_mult(x, x);
98
0
    let mut p = DoubleDouble::quick_mul_add(
99
0
        x2,
100
0
        DoubleDouble::from_bit_pair(C[6]),
101
0
        DoubleDouble::from_bit_pair(C[5]),
102
    );
103
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[4]));
104
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[3]));
105
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[2]));
106
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[1]));
107
0
    p = DoubleDouble::quick_mul_add_f64(x2, p, f64::from_bits(0x3ff0000000000000));
108
0
    let r = DoubleDouble::quick_mult(p, x);
109
0
    DoubleDouble::from_exact_add(r.hi, r.lo)
110
0
}
111
112
#[cold]
113
0
fn tan_accurate(y: DoubleDouble, tan_k: DoubleDouble) -> f64 {
114
    // Computes tan(x) through identities
115
    // tan(a+b) = (tan(a) + tan(b)) / (1 - tan(a)tan(b)) = (tan(y) + tan(k*pi/128)) / (1 - tan(y)*tan(k*pi/128))
116
0
    let tan_y = tan_eval_dd(y);
117
118
    // num = tan(y) + tan(k*pi/64)
119
0
    let num_dd = DoubleDouble::full_dd_add(tan_y, tan_k);
120
    // den = 1 - tan(y)*tan(k*pi/64)
121
0
    let den_dd = DoubleDouble::mul_add_f64(tan_y, -tan_k, 1.);
122
123
0
    let tan_x = DoubleDouble::div(num_dd, den_dd);
124
0
    tan_x.to_f64()
125
0
}
126
127
#[cold]
128
0
fn tan_near_zero_hard(x: f64) -> DoubleDouble {
129
    // Generated by Sollya:
130
    // d = [0, 0.03125];
131
    // f_tan = tan(x)/x;
132
    // Q = fpminimax(f_tan, [|0, 2, 4, 6, 8, 10, 12|], [|1, 107...|], d);
133
    const C: [(u64, u64); 7] = [
134
        (0x0000000000000000, 0x3ff0000000000000),
135
        (0x3c7555548455da94, 0x3fd5555555555555),
136
        (0x3c4306a6358cc810, 0x3fc1111111111111),
137
        (0x3c2ca9cd025ea98c, 0x3faba1ba1ba1b952),
138
        (0x3c3cb012803c55a5, 0x3f9664f4883eb962),
139
        (0x3c28afc1adb8f202, 0x3f8226e276097461),
140
        (0xbbdf8f911392f348, 0x3f6d7791601277ea),
141
    ];
142
0
    let x2 = DoubleDouble::from_exact_mult(x, x);
143
0
    let mut p = DoubleDouble::quick_mul_add(
144
0
        x2,
145
0
        DoubleDouble::from_bit_pair(C[6]),
146
0
        DoubleDouble::from_bit_pair(C[5]),
147
    );
148
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[4]));
149
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[3]));
150
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[2]));
151
0
    p = DoubleDouble::quick_mul_add(x2, p, DoubleDouble::from_bit_pair(C[1]));
152
0
    p = DoubleDouble::quick_mul_add_f64(x2, p, f64::from_bits(0x3ff0000000000000));
153
0
    DoubleDouble::quick_mult_f64(p, x)
154
0
}
155
156
/// Tangent in double precision
157
///
158
/// ULP 0.5
159
0
pub fn f_tan(x: f64) -> f64 {
160
0
    let x_e = (x.to_bits() >> 52) & 0x7ff;
161
    const E_BIAS: u64 = (1u64 << (11 - 1u64)) - 1u64;
162
163
    let y: DoubleDouble;
164
    let k;
165
166
0
    let mut argument_reduction = LargeArgumentReduction::default();
167
168
0
    if x_e < E_BIAS + 16 {
169
        // |x| < 2^16
170
0
        if x_e < E_BIAS - 5 {
171
            // |x| < 2^-5
172
0
            if x_e < E_BIAS - 27 {
173
                // |x| < 2^-27, |tan(x) - x| < ulp(x)/2.
174
0
                if x == 0.0 {
175
                    // Signed zeros.
176
0
                    return x + x;
177
0
                }
178
0
                return dyad_fmla(x, f64::from_bits(0x3c90000000000000), x);
179
0
            }
180
0
            let x2 = x * x;
181
0
            let x4 = x2 * x2;
182
            // Generated by Sollya:
183
            // d = [0, 0.03125];
184
            // f_tan = tan(x)/x;
185
            // Q = fpminimax(f_tan, [|0, 2, 4, 6, 8|], [|D...|], d);
186
            const C: [u64; 4] = [
187
                0x3fd555555555554b,
188
                0x3fc1111111142d5b,
189
                0x3faba1b9860ed843,
190
                0x3f966a802210f5bb,
191
            ];
192
0
            let p01 = f_fmla(x2, f64::from_bits(C[1]), f64::from_bits(C[0]));
193
0
            let p23 = f_fmla(x2, f64::from_bits(C[3]), f64::from_bits(C[2]));
194
0
            let w0 = f_fmla(x4, p23, p01);
195
0
            let w1 = x2 * w0 * x;
196
0
            let r = DoubleDouble::from_exact_add(x, w1);
197
0
            let err = f_fmla(
198
0
                x2,
199
0
                f64::from_bits(0x3cb0000000000000), // 2^-52
200
0
                f64::from_bits(0x3be0000000000000), // 2^-65
201
            );
202
0
            let ub = r.hi + (r.lo + err);
203
0
            let lb = r.hi + (r.lo - err);
204
0
            if ub == lb {
205
0
                return ub;
206
0
            }
207
0
            return tan_near_zero_hard(x).to_f64();
208
0
        } else {
209
0
            // Small range reduction.
210
0
            (y, k) = range_reduction_small(x);
211
0
        }
212
    } else {
213
        // Inf or NaN
214
0
        if x_e > 2 * E_BIAS {
215
0
            if x.is_nan() {
216
0
                return f64::NAN;
217
0
            }
218
            // tan(+-Inf) = NaN
219
0
            return x + f64::NAN;
220
0
        }
221
222
        // Large range reduction.
223
0
        (k, y) = argument_reduction.reduce(x);
224
    }
225
226
0
    let (tan_y, err) = tan_eval(y);
227
228
    // Computes tan(x) through identities.
229
    // tan(a+b) = (tan(a) + tan(b)) / (1 - tan(a)tan(b)) = (tan(y) + tan(k*pi/128)) / (1 - tan(y)*tan(k*pi/128))
230
0
    let tan_k = DoubleDouble::from_bit_pair(TAN_K_PI_OVER_128[(k & 255) as usize]);
231
232
    // num = tan(y) + tan(k*pi/64)
233
0
    let num_dd = DoubleDouble::add(tan_y, tan_k);
234
    // den = 1 - tan(y)*tan(k*pi/64)
235
0
    let den_dd = DoubleDouble::mul_add_f64(tan_y, -tan_k, 1.);
236
237
0
    let tan_x = DoubleDouble::div(num_dd, den_dd);
238
239
    // Simple error bound: |1 / den_dd| < 2^(1 + floor(-log2(den_dd)))).
240
0
    let den_inv = ((E_BIAS + 1) << (52 + 1)) - (den_dd.hi.to_bits() & EXP_MASK);
241
    // For tan_x = (num_dd + err) / (den_dd + err), the error is bounded by:
242
    //   | tan_x - num_dd / den_dd |  <= err * ( 1 + | tan_x * den_dd | ).
243
0
    let tan_err = err * f_fmla(f64::from_bits(den_inv), tan_x.hi.abs(), 1.0);
244
245
0
    let err_higher = tan_x.lo + tan_err;
246
0
    let err_lower = tan_x.lo - tan_err;
247
248
0
    let tan_upper = tan_x.hi + err_higher;
249
0
    let tan_lower = tan_x.hi + err_lower;
250
251
    // Ziv_s rounding test.
252
0
    if tan_upper == tan_lower {
253
0
        return tan_upper;
254
0
    }
255
256
0
    tan_accurate(y, tan_k)
257
0
}
258
259
#[cfg(test)]
260
mod tests {
261
    use super::*;
262
263
    #[test]
264
    fn tan_test() {
265
        assert_eq!(f_tan(0.0003242153424), 0.0003242153537600293);
266
        assert_eq!(f_tan(-0.3242153424), -0.3360742441422686);
267
        assert_eq!(f_tan(0.3242153424), 0.3360742441422686);
268
        assert_eq!(f_tan(-97301943219974110.), 0.000000000000000481917514979303);
269
        assert_eq!(f_tan(0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007291122019556397),
270
                   0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007291122019556397);
271
        assert_eq!(f_tan(0.0), 0.0);
272
        assert_eq!(f_tan(0.4324324324), 0.4615683710506729);
273
        assert_eq!(f_tan(1.0), 1.5574077246549023);
274
        assert_eq!(f_tan(-0.5), -0.5463024898437905);
275
        assert!(f_tan(f64::INFINITY).is_nan());
276
        assert!(f_tan(f64::NEG_INFINITY).is_nan());
277
        assert!(f_tan(f64::NAN).is_nan());
278
    }
279
}