/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 | | } |