1#![allow(clippy::indexing_slicing)]
13#![allow(clippy::excessive_precision)]
14#![allow(clippy::approx_constant)]
15#![allow(clippy::eq_op)]
16
17use super::{k_cos, k_sin, rem_pio2};
18use crate::Real;
19
20pub const fn sin(x: Real) -> Real {
43 let ix = (Real::to_bits(x) >> 32) as u32 & 0x7fffffff;
45
46 if ix <= 0x3fe921fb {
48 if ix < 0x3e500000 {
49 return x;
51 }
52 return k_sin(x, 0.0, 0);
53 }
54
55 if ix >= 0x7ff00000 {
57 return x - x;
58 }
59
60 let (n, y0, y1) = rem_pio2(x);
62 match n & 3 {
63 0 => k_sin(y0, y1, 1),
64 1 => k_cos(y0, y1),
65 2 => -k_sin(y0, y1, 1),
66 _ => -k_cos(y0, y1),
67 }
68}
69
70#[cfg(all(test, feature = "std"))]
71mod sin_tests {
72 use super::*;
73
74 const MAX_ULP: u64 = 1;
75
76 fn ulp_diff(a: f64, b: f64) -> u64 {
78 if a.is_nan() && b.is_nan() {
79 return 0;
80 }
81 if a.is_infinite() || b.is_infinite() {
82 return if a == b { 0 } else { u64::MAX };
83 }
84
85 let a_bits = a.to_bits();
86 let b_bits = b.to_bits();
87
88 if (a_bits | b_bits) & 0x7fff_ffff_ffff_ffff == 0 {
89 return 0;
90 }
91
92 if (a_bits ^ b_bits) & 0x8000_0000_0000_0000 != 0 {
93 return u64::MAX;
94 }
95
96 a_bits.abs_diff(b_bits)
97 }
98
99 fn check_ulp(x: f64) {
100 let expected = x.sin();
101 let actual = sin(x);
102
103 if expected.is_nan() {
104 assert!(actual.is_nan(), "sin({x}) should be NaN, got {actual}");
105 return;
106 }
107
108 if x.is_finite() {
109 assert!(
110 (-1.0000001..=1.0000001).contains(&actual),
111 "sin({x}) = {actual} is outside reasonable [-1, 1] range"
112 );
113 }
114
115 let ulps = ulp_diff(actual, expected);
116 assert!(
117 ulps <= MAX_ULP,
118 "sin({x}) failed: expected = {expected:.17e}, got = {actual:.17e}, ULP diff = {ulps} (max allowed {MAX_ULP})"
119 );
120 }
121
122 #[test]
127 fn sin_edge_cases() {
128 let pi = std::f64::consts::PI;
129 let pi_over_2 = std::f64::consts::FRAC_PI_2;
130
131 assert_eq!(sin(0.0), 0.0);
132 assert_eq!(sin(-0.0), -0.0);
133 assert!((sin(1.0) - 1.0f64.sin()).abs() < 1e-15);
134
135 assert!((sin(pi_over_2) - 1.0).abs() < 1e-14);
137 assert!((sin(-pi_over_2) + 1.0).abs() < 1e-14);
138 assert!((sin(pi) - 0.0).abs() < 1e-14);
139 assert!((sin(3.0 * pi_over_2) + 1.0).abs() < 1e-14);
140
141 assert_eq!(sin(1e-300), 1e-300);
143
144 let large = 1e10;
146 let diff = (sin(large) - large.sin()).abs();
147 assert!(diff < 1e-6 || sin(large).is_nan());
148
149 let neg_large = -1e8;
150 assert!((sin(neg_large) - neg_large.sin()).abs() < 1e-5);
151
152 let huge = 1e300;
154 let s = sin(huge);
155 assert!(s.is_nan() || s.abs() <= 1.0 + 1e-9);
156 }
157
158 #[test]
159 fn sin_very_large_arguments() {
160 let large_values: &[f64] = &[
163 1e40,
164 1e80,
165 1e120,
166 1e160,
167 1e200,
168 -1e50,
169 -1e100,
170 -1e150,
171 1e10 + std::f64::consts::PI * 1e8, -1e12 - std::f64::consts::PI * 1e7,
173 ];
174
175 for &x in large_values {
176 let our = sin(x);
177 let std_val = x.sin();
178
179 if our.is_nan() && std_val.is_nan() {
180 continue;
181 }
182
183 let diff = (our - std_val).abs();
185 assert!(
186 diff < 1e-5 || our.is_nan(),
187 "sin mismatch on very large argument at x = {}: diff = {}",
188 x,
189 diff
190 );
191 }
192 }
193
194 #[test]
195 fn sin_identities() {
196 let x = 1.23456789;
197
198 assert!((sin(-x) + sin(x)).abs() < 1e-15);
199 assert!((sin(x + 2.0 * std::f64::consts::PI) - sin(x)).abs() < 1e-9);
200 }
201
202 #[test]
203 fn sin_monotonicity() {
204 let tol = 1e-12;
205
206 let mut prev = sin(-1.0);
208 for i in 1..100_000 {
209 let x = -1.0 + (i as f64) * 2e-5;
210 let y = sin(x);
211 assert!(y + tol >= prev, "Non-monotonic (increasing) at x = {}", x);
212 prev = y;
213 }
214
215 prev = sin(std::f64::consts::FRAC_PI_2 + 0.1);
217 for i in 1..100_000 {
218 let x = std::f64::consts::FRAC_PI_2 + 0.1 + (i as f64) * 2e-5;
219 let y = sin(x);
220 assert!(y + tol <= prev, "Non-monotonic (decreasing) at x = {}", x);
221 prev = y;
222 }
223 }
224
225 #[test]
226 fn sin_very_small_values() {
227 for i in 0..30 {
229 let x = 1e-20 * (i as f64 + 1.0);
230 assert_eq!(sin(x), x, "Failed at x = {}", x);
231 }
232
233 assert_eq!(sin(1e-300), 1e-300);
235 assert_eq!(sin(-1e-250), -1e-250);
236 }
237
238 #[test]
239 fn sin_hard_reduction_cases() {
240 let cases: &[f64] = &[
241 1.5707963267948966,
242 4.71238898038469,
243 1e10 + 0.5,
244 std::f64::consts::PI * 1e8,
245 -std::f64::consts::PI * 1e7 + 1e-9,
246 1e20,
247 -1e20,
248 ];
249
250 for &x in cases {
251 let our = sin(x);
252 let std = x.sin();
253 let diff = (our - std).abs();
254 assert!(
255 diff < 1e-8 || our.is_nan(),
256 "Hard reduction case failed at x = {}: diff = {}",
257 x,
258 diff
259 );
260 }
261 }
262
263 #[test]
264 fn sin_near_pi_over_2() {
265 let pi_over_2 = std::f64::consts::FRAC_PI_2;
266
267 for i in 0..100_000 {
268 let delta = (i as f64 - 50_000.0) * 1e-11;
269 let x = pi_over_2 + delta;
270 let our = sin(x);
271 let expected = x.sin();
272 let diff = (our - expected).abs();
273 assert!(
274 diff < 1e-10,
275 "Large error near π/2 at x = {}: diff = {}",
276 x,
277 diff
278 );
279 }
280 }
281
282 #[test]
283 fn sin_near_multiples_of_pi() {
284 let pi = std::f64::consts::PI;
285
286 for k in -10i32..=10 {
287 let base = (k as f64) * pi;
288
289 for &delta in &[1e-9, 1e-8, -1e-9, -1e-8] {
291 let x = base + delta;
292 let our = sin(x);
293 let std = x.sin();
294 let diff = (our - std).abs();
295 assert!(
296 diff < 1e-9 || our.is_nan(),
297 "Error near {}π at x = {}: diff = {}",
298 k,
299 x,
300 diff
301 );
302 }
303 }
304 }
305
306 #[test]
307 fn sin_ulp_accuracy() {
308 let pi = std::f64::consts::PI;
309 let pi_over_2 = std::f64::consts::FRAC_PI_2;
310 let pi_over_4 = std::f64::consts::FRAC_PI_4;
311
312 for i in -2000..=2000 {
314 check_ulp((i as f64 / 2000.0) * pi_over_4);
315 }
316
317 for k in -20..=20 {
319 let base = (k as f64) * pi + pi_over_2;
320 for i in -100..=100 {
321 check_ulp(base + (i as f64) * 1e-10);
322 }
323 }
324
325 for k in 0..=30 {
327 let base = (k as f64) * pi;
328 for i in -50..=50 {
329 check_ulp(base + (i as f64) * 0.012345);
330 }
331 }
332
333 let mut x = 1e6_f64;
335 while x < 1e22 {
336 check_ulp(x);
337 check_ulp(x + 0.123456789);
338 check_ulp(-x);
339 x *= 3.1415926535;
340 }
341
342 let mut walk = 0.987654321_f64;
344 for _ in 0..5_000 {
345 check_ulp(walk);
346 walk = walk * 1.618033988749895 + 0.2718281828459045;
347 if walk.abs() > 1e16 {
348 walk = walk.fract() * 100.0;
349 }
350 }
351 }
352
353 #[test]
354 fn sin_scale_coverage() {
355 let pi = std::f64::consts::PI;
356 let pi_over_2 = std::f64::consts::FRAC_PI_2;
357 let pi_over_4 = std::f64::consts::FRAC_PI_4;
358
359 let scales = [1.0, 10.0, 100.0, 1_000.0, 1e6, 1e8, 1e10];
360
361 let quadrant_offsets = [0.0, pi_over_4, pi_over_2, pi, 3.0 * pi_over_2, 2.0 * pi];
364
365 let mut cases = Vec::new();
366
367 for &scale in &scales {
368 for &offset in &quadrant_offsets {
369 cases.push(scale * offset);
370 cases.push(-scale * offset);
371 cases.push(scale + offset);
372 cases.push(scale - offset);
373 }
374
375 for k in 1..=5 {
377 cases.push(scale * pi * (k as f64) / 11.0);
378 cases.push(-scale * pi * (k as f64) / 11.0);
379 }
380 }
381
382 for i in 0..8 {
384 cases.push(1e-20 * (i as f64 + 1.0));
385 cases.push(-1e-20 * (i as f64 + 1.0));
386 }
387
388 for &x in &cases {
389 check_ulp(x);
390 }
391 }
392
393 const _: () = {
395 let _ = sin(0.0);
396 let _ = sin(1.0);
397 let _ = sin(-std::f64::consts::PI);
398 };
399}