/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.25/src/tangent/tanpi.rs
Line | Count | Source |
1 | | /* |
2 | | * // Copyright (c) Radzivon Bartoshyk 7/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::f_fmla; |
30 | | use crate::double_double::DoubleDouble; |
31 | | use crate::sincospi::reduce_pi_64; |
32 | | use crate::tangent::tanpi_table::TANPI_K_PI_OVER_64; |
33 | | |
34 | | #[inline] |
35 | 0 | pub(crate) fn tanpi_eval(x: f64) -> DoubleDouble { |
36 | 0 | let x2 = DoubleDouble::from_exact_mult(x, x); |
37 | | // tan(pi*x) generated by Sollya: |
38 | | // d = [0, 0.0078128]; |
39 | | // f_tan = tan(y*pi)/y; |
40 | | // Q = fpminimax(f_tan, [|0, 2, 4, 6, 8|], [|107, D...|], d, relative, floating); |
41 | | const C: [u64; 4] = [ |
42 | | 0x4024abbce625be51, |
43 | | 0x404466bc677698e5, |
44 | | 0x40645fff70379ae3, |
45 | | 0x4084626b091b7fd0, |
46 | | ]; |
47 | | const C0: DoubleDouble = DoubleDouble::from_bit_pair((0x3ca1a6444aa5b996, 0x400921fb54442d18)); |
48 | | |
49 | | // polyeval 4, estrin scheme |
50 | 0 | let u0 = f_fmla(x2.hi, f64::from_bits(C[1]), f64::from_bits(C[0])); |
51 | 0 | let u1 = f_fmla(x2.hi, f64::from_bits(C[3]), f64::from_bits(C[2])); |
52 | 0 | let tan_poly_lo = f_fmla(x2.hi * x2.hi, u1, u0); |
53 | | |
54 | | // We're splitting polynomial in two parts, since first term dominates |
55 | | // we compute: (a0_lo + a0_hi) * x + x * (a1 * x^2 + a2 + x^4) ... |
56 | 0 | let r_lo = DoubleDouble::quick_mult_f64(x2, tan_poly_lo); |
57 | 0 | let tan_lo = f_fmla(r_lo.lo, x, r_lo.hi * x); |
58 | 0 | let tan_hi = DoubleDouble::quick_mult_f64(C0, x); |
59 | 0 | DoubleDouble::full_add_f64(tan_hi, tan_lo) |
60 | 0 | } |
61 | | |
62 | | #[cold] |
63 | 0 | fn tanpi_hard(x: f64, tan_k: DoubleDouble) -> DoubleDouble { |
64 | | const C: [(u64, u64); 6] = [ |
65 | | (0x3ca1a62632712fc8, 0x400921fb54442d18), |
66 | | (0xbcc052338fbb4528, 0x4024abbce625be53), |
67 | | (0x3ced42454c5f85b3, 0x404466bc6775aad9), |
68 | | (0xbd00c7d6a971a560, 0x40645fff9b4b244d), |
69 | | (0x3d205970eff53274, 0x40845f46e96c3a0b), |
70 | | (0xbd3589489ad24fc4, 0x40a4630551cd123d), |
71 | | ]; |
72 | 0 | let x2 = DoubleDouble::from_exact_mult(x, x); |
73 | 0 | let mut tan_y = DoubleDouble::quick_mul_add( |
74 | 0 | x2, |
75 | 0 | DoubleDouble::from_bit_pair(C[5]), |
76 | 0 | DoubleDouble::from_bit_pair(C[4]), |
77 | | ); |
78 | 0 | tan_y = DoubleDouble::quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[3])); |
79 | 0 | tan_y = DoubleDouble::quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[2])); |
80 | 0 | tan_y = DoubleDouble::quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[1])); |
81 | 0 | tan_y = DoubleDouble::quick_mul_add(x2, tan_y, DoubleDouble::from_bit_pair(C[0])); |
82 | 0 | tan_y = DoubleDouble::quick_mult_f64(tan_y, x); |
83 | | |
84 | | // num = tan(y*pi/64) + tan(k*pi/64) |
85 | 0 | let num = DoubleDouble::full_dd_add(tan_y, tan_k); |
86 | | // den = 1 - tan(y*pi/64)*tan(k*pi/64) |
87 | 0 | let den = DoubleDouble::mul_add_f64(tan_y, -tan_k, 1.); |
88 | | // tan = num / den |
89 | 0 | DoubleDouble::div(num, den) |
90 | 0 | } |
91 | | |
92 | | /// Computes tan(PI*x) |
93 | | /// |
94 | | /// Max found ULP 0.5 |
95 | 0 | pub fn f_tanpi(x: f64) -> f64 { |
96 | 0 | if x == 0. { |
97 | 0 | return x; |
98 | 0 | } |
99 | 0 | let ax = x.to_bits() & 0x7fff_ffff_ffff_ffff; |
100 | 0 | if ax >= (0x7ffu64 << 52) { |
101 | | // NaN, Inf |
102 | 0 | if ax > (0x7ffu64 << 52) { |
103 | 0 | return x + x; |
104 | 0 | } // NaN |
105 | 0 | return f64::NAN; // x=Inf |
106 | 0 | } |
107 | 0 | let e: i32 = (ax >> 52) as i32 - 1023; |
108 | 0 | if e > 0 { |
109 | 0 | if e >= 52 { |
110 | | // when |x| > 2^53 it's always an integer |
111 | 0 | return f64::copysign(0., x); |
112 | 0 | } |
113 | | // |x| > 1 and |x| < 2^53 |
114 | 0 | let m = (ax & ((1u64 << 52) - 1)) | (1u64 << 52); // mantissa with hidden 1 |
115 | 0 | let shift = 52 - e; |
116 | | |
117 | 0 | let frac = m & ((1u64 << shift) - 1); |
118 | 0 | if frac == (1u64 << (shift - 1)) { |
119 | | // |x| is always integer.5 means it's inf |
120 | 0 | return f64::INFINITY; |
121 | 0 | } |
122 | 0 | } |
123 | | |
124 | 0 | if ax <= 0x3cb0000000000000 { |
125 | | // for tiny x ( |x| < f64::EPSILON ) just small taylor expansion |
126 | | // tan(PI*x) ~ PI*x + PI^3*x^3/3 + O(x^5) |
127 | | const PI: DoubleDouble = |
128 | | DoubleDouble::from_bit_pair((0x3ca1a62633145c07, 0x400921fb54442d18)); |
129 | 0 | if ax <= 0x3ca0000000000000 { |
130 | | // |x| <= 2^-53, renormalize value |
131 | 0 | let e: i32 = (ax >> 52) as i32; |
132 | 0 | let sc = f64::from_bits((2045i64 - e as i64).wrapping_shl(52) as u64); |
133 | 0 | let isc = f64::from_bits(1i64.wrapping_add(e as i64).wrapping_shl(52) as u64); |
134 | 0 | let dx = x * sc; |
135 | 0 | let q0 = DoubleDouble::quick_mult_f64(PI, dx); |
136 | 0 | let r = q0.to_f64() * isc; |
137 | 0 | return r; |
138 | 0 | } |
139 | 0 | let q0 = DoubleDouble::quick_mult_f64(PI, x); |
140 | 0 | let r = q0.to_f64(); |
141 | 0 | return r; |
142 | 0 | } |
143 | | |
144 | | // argument reduction |
145 | 0 | let (y, k) = reduce_pi_64(x); |
146 | | |
147 | 0 | if y == 0.0 { |
148 | 0 | let km = (k.abs() & 63) as i32; // k mod 64 |
149 | | |
150 | 0 | match km { |
151 | 0 | 0 => return f64::copysign(0f64, x), // tanpi(n) = 0 |
152 | 0 | 32 => return f64::copysign(f64::INFINITY, x), // tanpi(n+0.5) = ±∞ |
153 | 0 | 16 => return f64::copysign(1.0, x), // tanpi(n+0.25) = ±1 |
154 | 0 | 48 => return -f64::copysign(1.0, x), // tanpi(n+0.75) = ∓1 |
155 | 0 | _ => {} |
156 | | } |
157 | 0 | } |
158 | | |
159 | 0 | let tan_k = DoubleDouble::from_bit_pair(TANPI_K_PI_OVER_64[((k as u64) & 127) as usize]); |
160 | | |
161 | | // Computes tan(pi*x) through identities. |
162 | | // tan(a+b) = (tan(a) + tan(b)) / (1 - tan(a)tan(b)) = (tan(y*pi/64) + tan(k*pi/64)) / (1 - tan(y*pi/64)*tan(k*pi/64)) |
163 | 0 | let tan_y = tanpi_eval(y); |
164 | | // num = tan(y*pi/64) + tan(k*pi/64) |
165 | 0 | let num = DoubleDouble::add(tan_k, tan_y); |
166 | | // den = 1 - tan(y*pi/64)*tan(k*pi/64) |
167 | 0 | let den = DoubleDouble::mul_add_f64(tan_y, -tan_k, 1.); |
168 | | // tan = num / den |
169 | 0 | let tan_value = DoubleDouble::div(num, den); |
170 | 0 | let err = f_fmla( |
171 | 0 | tan_value.hi, |
172 | 0 | f64::from_bits(0x3bf0000000000000), // 2^-64 |
173 | 0 | f64::from_bits(0x3b60000000000000), // 2^-73 |
174 | | ); |
175 | 0 | let ub = tan_value.hi + (tan_value.lo + err); |
176 | 0 | let lb = tan_value.hi + (tan_value.lo - err); |
177 | 0 | if ub == lb { |
178 | 0 | return tan_value.to_f64(); |
179 | 0 | } |
180 | 0 | tanpi_hard(y, tan_k).to_f64() |
181 | 0 | } |
182 | | |
183 | | #[cfg(test)] |
184 | | mod tests { |
185 | | use super::*; |
186 | | |
187 | | #[test] |
188 | | fn test_tanpi() { |
189 | | assert_eq!(f_tanpi(0.4999999999119535), 3615246871.564404); |
190 | | assert_eq!(f_tanpi(7119681148991743.0), 0.); |
191 | | assert_eq!(f_tanpi(63.5), f64::INFINITY); |
192 | | assert_eq!(f_tanpi(63.99935913085936), -0.0020133525045719896); |
193 | | assert_eq!(f_tanpi(3.3821122649309461E-306), 1.0625219045122997E-305); |
194 | | assert_eq!(f_tanpi(1.8010707049867402E-255), 5.6582304953821333E-255); |
195 | | assert_eq!(f_tanpi(1.001000000061801), 0.0031416031832113213); |
196 | | assert_eq!(f_tanpi(-0.5000000000000226), 14054316517702.594); |
197 | | assert_eq!(f_tanpi(0.5000000000000001), -2867080569611329.5); |
198 | | assert_eq!(f_tanpi(0.02131), 0.06704753721009375); |
199 | | assert!(f_tanpi(f64::INFINITY).is_nan()); |
200 | | assert!(f_tanpi(f64::NAN).is_nan()); |
201 | | assert!(f_tanpi(f64::NEG_INFINITY).is_nan()); |
202 | | } |
203 | | } |