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 #[allow(dead_code)]
79 struct Rng(u64);
80
81 #[allow(dead_code)]
82 impl Rng {
83 fn new(seed: u64) -> Self {
84 Self(seed)
85 }
86
87 #[inline]
88 fn next_u64(&mut self) -> u64 {
89 self.0 = self.0.wrapping_add(0x9E3779B97F4A7C15);
90 let mut z = self.0;
91 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
92 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
93 z ^ (z >> 31)
94 }
95
96 fn next_f64(&mut self, scale: f64) -> f64 {
98 let bits = self.next_u64();
99 let mantissa = bits & 0x000f_ffff_ffff_ffff;
100 let f = f64::from_bits(0x3ff0_0000_0000_0000 | mantissa);
101 (f - 1.0) * 2.0 * scale - scale
102 }
103 }
104
105 #[allow(dead_code)]
110 fn ulp_diff(a: f64, b: f64) -> u64 {
111 if a.is_nan() && b.is_nan() {
112 return 0;
113 }
114 if a.is_infinite() && b.is_infinite() && a.signum() == b.signum() {
115 return 0;
116 }
117 a.to_bits().abs_diff(b.to_bits())
118 }
119
120 #[test]
125 fn sin_edge_cases() {
126 let pi = std::f64::consts::PI;
127 let pi_over_2 = std::f64::consts::FRAC_PI_2;
128
129 assert_eq!(sin(0.0), 0.0);
130 assert_eq!(sin(-0.0), -0.0);
131 assert!((sin(1.0) - 1.0f64.sin()).abs() < 1e-15);
132
133 assert!((sin(pi_over_2) - 1.0).abs() < 1e-14);
135 assert!((sin(-pi_over_2) + 1.0).abs() < 1e-14);
136 assert!((sin(pi) - 0.0).abs() < 1e-14);
137 assert!((sin(3.0 * pi_over_2) + 1.0).abs() < 1e-14);
138
139 assert_eq!(sin(1e-300), 1e-300);
141
142 let large = 1e10;
144 let diff = (sin(large) - large.sin()).abs();
145 assert!(diff < 1e-6 || sin(large).is_nan());
146
147 let neg_large = -1e8;
148 assert!((sin(neg_large) - neg_large.sin()).abs() < 1e-5);
149
150 let huge = 1e300;
152 let s = sin(huge);
153 assert!(s.is_nan() || s.abs() <= 1.0 + 1e-9);
154 }
155
156 #[test]
157 fn sin_very_large_arguments() {
158 let large_values: &[f64] = &[
161 1e40,
162 1e80,
163 1e120,
164 1e160,
165 1e200,
166 -1e50,
167 -1e100,
168 -1e150,
169 1e10 + std::f64::consts::PI * 1e8, -1e12 - std::f64::consts::PI * 1e7,
171 ];
172
173 for &x in large_values {
174 let our = sin(x);
175 let std_val = x.sin();
176
177 if our.is_nan() && std_val.is_nan() {
178 continue;
179 }
180
181 let diff = (our - std_val).abs();
183 assert!(
184 diff < 1e-5 || our.is_nan(),
185 "sin mismatch on very large argument at x = {}: diff = {}",
186 x,
187 diff
188 );
189 }
190 }
191
192 #[test]
193 fn sin_identities() {
194 let x = 1.23456789;
195
196 assert!((sin(-x) + sin(x)).abs() < 1e-15);
197 assert!((sin(x + 2.0 * std::f64::consts::PI) - sin(x)).abs() < 1e-9);
198 }
199
200 #[test]
201 fn sin_monotonicity() {
202 let tol = 1e-12;
203
204 let mut prev = sin(-1.0);
206 for i in 1..100_000 {
207 let x = -1.0 + (i as f64) * 2e-5;
208 let y = sin(x);
209 assert!(y + tol >= prev, "Non-monotonic (increasing) at x = {}", x);
210 prev = y;
211 }
212
213 prev = sin(std::f64::consts::FRAC_PI_2 + 0.1);
215 for i in 1..100_000 {
216 let x = std::f64::consts::FRAC_PI_2 + 0.1 + (i as f64) * 2e-5;
217 let y = sin(x);
218 assert!(y + tol <= prev, "Non-monotonic (decreasing) at x = {}", x);
219 prev = y;
220 }
221 }
222
223 #[test]
224 fn sin_very_small_values() {
225 for i in 0..30 {
227 let x = 1e-20 * (i as f64 + 1.0);
228 assert_eq!(sin(x), x, "Failed at x = {}", x);
229 }
230
231 assert_eq!(sin(1e-300), 1e-300);
233 assert_eq!(sin(-1e-250), -1e-250);
234 }
235
236 #[test]
237 fn sin_hard_reduction_cases() {
238 let cases: &[f64] = &[
239 1.5707963267948966,
240 4.71238898038469,
241 1e10 + 0.5,
242 std::f64::consts::PI * 1e8,
243 -std::f64::consts::PI * 1e7 + 1e-9,
244 1e20,
245 -1e20,
246 ];
247
248 for &x in cases {
249 let our = sin(x);
250 let std = x.sin();
251 let diff = (our - std).abs();
252 assert!(
253 diff < 1e-8 || our.is_nan(),
254 "Hard reduction case failed at x = {}: diff = {}",
255 x,
256 diff
257 );
258 }
259 }
260
261 #[test]
262 fn sin_near_pi_over_2() {
263 let pi_over_2 = std::f64::consts::FRAC_PI_2;
264
265 for i in 0..100_000 {
266 let delta = (i as f64 - 50_000.0) * 1e-11;
267 let x = pi_over_2 + delta;
268 let our = sin(x);
269 let expected = x.sin();
270 let diff = (our - expected).abs();
271 assert!(
272 diff < 1e-10,
273 "Large error near π/2 at x = {}: diff = {}",
274 x,
275 diff
276 );
277 }
278 }
279
280 #[test]
281 fn sin_near_multiples_of_pi() {
282 let pi = std::f64::consts::PI;
283
284 for k in -10i32..=10 {
285 let base = (k as f64) * pi;
286
287 for &delta in &[1e-9, 1e-8, -1e-9, -1e-8] {
289 let x = base + delta;
290 let our = sin(x);
291 let std = x.sin();
292 let diff = (our - std).abs();
293 assert!(
294 diff < 1e-9 || our.is_nan(),
295 "Error near {}π at x = {}: diff = {}",
296 k,
297 x,
298 diff
299 );
300 }
301 }
302 }
303
304 #[test]
305 fn sin_max_ulp_error() {
306 let mut max_ulp: u64 = 0;
307 let mut worst_x = 0.0f64;
308 let mut rng = Rng::new(0xdead_beef_cafe_babe);
309
310 for _ in 0..5_000_000 {
311 let scale = if rng.next_u64() & 1 == 0 { 1e6 } else { 1e12 };
312 let x = rng.next_f64(scale);
313 let our = sin(x);
314 let std = x.sin();
315
316 if our.is_nan() || std.is_nan() {
317 continue;
318 }
319
320 let ulp = ulp_diff(our, std);
321 if ulp > max_ulp {
322 max_ulp = ulp;
323 worst_x = x;
324 }
325 }
326
327 assert!(
329 max_ulp <= 1,
330 "sin() maximum error too high: {} ULPs at x = {}",
331 max_ulp,
332 worst_x
333 );
334 }
335
336 #[test]
337 fn sin_random_many() {
338 let mut rng = Rng::new(0x1234_5678_9abc_def0);
339 let scales = [1.0, 10.0, 100.0, 1_000.0, 1e6, 1e8, 1e10];
340
341 for &scale in &scales {
342 for _ in 0..150_000 {
343 let x = rng.next_f64(scale);
344 let our = sin(x);
345 let std_val = x.sin();
346
347 if our.is_nan() && std_val.is_nan() {
348 continue;
349 }
350
351 let diff = if x.abs() < 1e-8 {
353 (our - std_val).abs()
354 } else {
355 (our - std_val).abs() / x.abs().max(1.0)
356 };
357
358 let tol = if x.abs() < 1e-8 { 1e-14 } else { 5e-12 };
359
360 assert!(
361 diff < tol,
362 "sin mismatch at x = {}: our={}, std={}, diff={}",
363 x,
364 our,
365 std_val,
366 diff
367 );
368 }
369 }
370 }
371
372 const _: () = {
374 let _ = sin(0.0);
375 let _ = sin(1.0);
376 let _ = sin(-std::f64::consts::PI);
377 };
378}