1use crate::constants::compute_pi;
9use crate::elementary::{convert_dbig_to_fbig, exp, fbig_to_dbig, truncate_to_precision};
10use crate::{DBig, OxiNumError, OxiNumResult};
11use std::str::FromStr;
12
13pub fn sin(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
30 validate_precision(precision)?;
31 let zero = make_dbig("0.0")?;
32 if *x == zero {
33 return Ok(zero);
34 }
35 let guard_prec = precision + 10;
37 let reduced = reduce_argument(x, guard_prec);
38 let result = sin_taylor(&reduced, guard_prec);
39 Ok(truncate_to_precision(result, precision))
40}
41
42pub fn cos(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
55 validate_precision(precision)?;
56 let zero = make_dbig("0.0")?;
57 if *x == zero {
58 return make_dbig("1.0");
59 }
60 let guard_prec = precision + 10;
61 let reduced = reduce_argument(x, guard_prec);
62 let result = cos_taylor(&reduced, guard_prec);
63 Ok(truncate_to_precision(result, precision))
64}
65
66pub fn tan(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
72 validate_precision(precision)?;
73 let guard_prec = precision + 10;
74 let s = sin(x, guard_prec)?;
75 let c = cos(x, guard_prec)?;
76 let zero = make_dbig("0.0")?;
77 if c == zero {
78 return Err(OxiNumError::DivByZero);
79 }
80 let result = &s / &c;
81 Ok(truncate_to_precision(result, precision))
82}
83
84pub fn atan(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
89 validate_precision(precision)?;
90 let zero = make_dbig("0.0")?;
91 if *x == zero {
92 return Ok(zero);
93 }
94
95 let guard_prec = precision + 15;
96 let one = make_dbig("1.0")?;
97 let neg_one = make_dbig("-1.0")?;
98
99 let is_negative = *x < zero;
100 let abs_x = if is_negative { x * &neg_one } else { x.clone() };
101
102 let result = if abs_x > one {
103 let pi = compute_pi(guard_prec);
105 let half_pi = &pi / &make_dbig("2.0")?;
106 let recip = &one / &abs_x;
107 let atan_recip = atan_small(&recip, guard_prec);
108 &half_pi - &atan_recip
109 } else {
110 atan_small(&abs_x, guard_prec)
111 };
112
113 let final_result = if is_negative {
114 &result * &neg_one
115 } else {
116 result
117 };
118 Ok(truncate_to_precision(final_result, precision))
119}
120
121pub fn atan2(y: &DBig, x: &DBig, precision: usize) -> OxiNumResult<DBig> {
126 validate_precision(precision)?;
127 let zero = make_dbig("0.0")?;
128 let guard_prec = precision + 10;
129 let pi = compute_pi(guard_prec);
130
131 if *x > zero {
132 let ratio = y / x;
133 atan(&ratio, precision)
134 } else if *x < zero && *y >= zero {
135 let ratio = y / x;
136 let a = atan(&ratio, guard_prec)?;
137 Ok(truncate_to_precision(&a + &pi, precision))
138 } else if *x < zero && *y < zero {
139 let ratio = y / x;
140 let a = atan(&ratio, guard_prec)?;
141 Ok(truncate_to_precision(&a - &pi, precision))
142 } else if *x == zero && *y > zero {
143 let half_pi = &pi / &make_dbig("2.0")?;
144 Ok(truncate_to_precision(half_pi, precision))
145 } else if *x == zero && *y < zero {
146 let neg_half_pi = &(&pi / &make_dbig("2.0")?) * &make_dbig("-1.0")?;
147 Ok(truncate_to_precision(neg_half_pi, precision))
148 } else {
149 Ok(zero)
151 }
152}
153
154pub fn sinh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
160 validate_precision(precision)?;
161 let guard_prec = precision + 10;
162 let exp_x = exp(x, guard_prec)?;
163 let neg_x = x * &make_dbig("-1.0")?;
164 let exp_neg_x = exp(&neg_x, guard_prec)?;
165 let diff = &exp_x - &exp_neg_x;
166 let result = &diff / &make_dbig("2.0")?;
167 Ok(truncate_to_precision(result, precision))
168}
169
170pub fn cosh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
172 validate_precision(precision)?;
173 let guard_prec = precision + 10;
174 let exp_x = exp(x, guard_prec)?;
175 let neg_x = x * &make_dbig("-1.0")?;
176 let exp_neg_x = exp(&neg_x, guard_prec)?;
177 let sum = &exp_x + &exp_neg_x;
178 let result = &sum / &make_dbig("2.0")?;
179 Ok(truncate_to_precision(result, precision))
180}
181
182pub fn tanh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
184 validate_precision(precision)?;
185 let guard_prec = precision + 10;
186 let s = sinh(x, guard_prec)?;
187 let c = cosh(x, guard_prec)?;
188 let result = &s / &c;
189 Ok(truncate_to_precision(result, precision))
190}
191
192fn sin_taylor(x: &DBig, precision: usize) -> DBig {
201 let x = extend_precision(x, precision);
202 let mut term = x.clone();
203 let mut sum = x.clone();
204 let x_sq = &x * &x;
205 let neg_one = dbig_at_precision("-1.0", precision);
206
207 for k in 1..=(precision as u32 + 20) {
208 let denom_a = dbig_u32_at_precision(2 * k, precision);
209 let denom_b = dbig_u32_at_precision(2 * k + 1, precision);
210 term = &term * &x_sq;
211 term = &term / &denom_a;
212 term = &term / &denom_b;
213 term = &term * &neg_one;
214 sum = &sum + &term;
215
216 if is_negligible(&term, precision) {
217 break;
218 }
219 }
220 sum
221}
222
223fn cos_taylor(x: &DBig, precision: usize) -> DBig {
228 let x = extend_precision(x, precision);
229 let one = dbig_at_precision("1.0", precision);
230 let mut term = one.clone();
231 let mut sum = one;
232 let x_sq = &x * &x;
233 let neg_one = dbig_at_precision("-1.0", precision);
234
235 for k in 1..=(precision as u32 + 20) {
236 let denom_a = dbig_u32_at_precision(2 * k - 1, precision);
237 let denom_b = dbig_u32_at_precision(2 * k, precision);
238 term = &term * &x_sq;
239 term = &term / &denom_a;
240 term = &term / &denom_b;
241 term = &term * &neg_one;
242 sum = &sum + &term;
243
244 if is_negligible(&term, precision) {
245 break;
246 }
247 }
248 sum
249}
250
251fn atan_small(x: &DBig, precision: usize) -> DBig {
257 let threshold = make_dbig("0.5").expect("valid literal");
259 let one = make_dbig("1.0").expect("valid literal");
260
261 let mut reduced = x.clone();
262 let mut halvings = 0u32;
263
264 while reduced > threshold {
265 let x_sq = &reduced * &reduced;
267 let inner = &one + &x_sq;
268 let guard_bits = precision * 4 + 20;
270 let fbig = convert_dbig_to_fbig(&inner, guard_bits);
271 let sqrt_val = dashu_base::SquareRoot::sqrt(&fbig);
272 let sqrt_dbig = fbig_to_dbig(&sqrt_val, precision);
273 reduced = &reduced / &(&one + &sqrt_dbig);
274 halvings += 1;
275
276 if halvings > 50 {
277 break; }
279 }
280
281 let mut result = atan_taylor_core(&reduced, precision);
282
283 for _ in 0..halvings {
285 result = &result * &make_dbig("2.0").expect("valid literal");
286 }
287
288 result
289}
290
291fn atan_taylor_core(x: &DBig, precision: usize) -> DBig {
297 let x = extend_precision(x, precision);
298 let mut power = x.clone();
299 let mut sum = x.clone();
300 let x_sq = &x * &x;
301 let neg_one = dbig_at_precision("-1.0", precision);
302 let mut sign = neg_one.clone();
303
304 for k in 1..=(precision as u32 * 3 + 50) {
305 power = &power * &x_sq;
306 let denom = dbig_u32_at_precision(2 * k + 1, precision);
307 let contrib = &power / &denom;
308 sum = &sum + &(&contrib * &sign);
309 sign = &sign * &neg_one;
310
311 if is_negligible(&contrib, precision) {
312 break;
313 }
314 }
315 sum
316}
317
318fn reduce_argument(x: &DBig, precision: usize) -> DBig {
324 let pi = compute_pi(precision.min(195) + 5);
325 let two = dbig_at_precision("2.0", precision + 5);
326 let two_pi = (&pi * &two).with_precision(precision + 5).value();
327 let zero = dbig_at_precision("0.0", precision + 5);
328
329 if two_pi == zero {
330 return x.clone();
331 }
332
333 let mut reduced = extend_precision(x, precision + 5);
335 if reduced > zero {
336 while reduced > two_pi {
337 reduced = (&reduced - &two_pi).with_precision(precision + 5).value();
338 }
339 } else {
340 while reduced < zero {
341 reduced = (&reduced + &two_pi).with_precision(precision + 5).value();
342 }
343 }
344 reduced
345}
346
347fn is_negligible(term: &DBig, precision: usize) -> bool {
349 let s = term.to_string();
350 let s = s.trim_start_matches('-');
351
352 if let Some(dot_pos) = s.find('.') {
353 let integer_part = &s[..dot_pos];
354 if integer_part == "0" {
355 let frac = &s[dot_pos + 1..];
356 let leading_zeros = frac.chars().take_while(|&c| c == '0').count();
357 return leading_zeros >= precision + 3;
358 }
359 }
360 s == "0" || s == "0.0"
361}
362
363fn make_dbig(s: &str) -> OxiNumResult<DBig> {
369 DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
370}
371
372fn extend_precision(value: &DBig, precision: usize) -> DBig {
375 value.clone().with_precision(precision).value()
376}
377
378fn dbig_at_precision(s: &str, precision: usize) -> DBig {
380 let v = DBig::from_str(s).expect("valid decimal literal");
381 v.with_precision(precision).value()
382}
383
384fn dbig_u32_at_precision(n: u32, precision: usize) -> DBig {
386 let v = DBig::from_str(&format!("{n}.0")).expect("integer.0 is always a valid decimal");
387 v.with_precision(precision).value()
388}
389
390fn validate_precision(precision: usize) -> OxiNumResult<()> {
391 if precision == 0 {
392 return Err(OxiNumError::Precision("precision must be > 0".into()));
393 }
394 Ok(())
395}
396
397#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn sin_of_zero() {
407 let x = make_dbig("0.0").expect("ok");
408 let result = sin(&x, 30).expect("ok");
409 assert_eq!(result, make_dbig("0.0").expect("ok"));
410 }
411
412 #[test]
413 fn cos_of_zero() {
414 let result = cos(&make_dbig("0.0").expect("ok"), 30).expect("ok");
415 let s = result.to_string();
416 assert!(s.starts_with("1"), "cos(0) = {s}");
417 }
418
419 #[test]
420 fn sin_of_pi_is_near_zero() {
421 let pi = compute_pi(40);
422 let result = sin(&pi, 20).expect("ok");
423 let s = result.to_string();
424 let s_trimmed = s.trim_start_matches('-');
425 assert!(
426 s_trimmed.starts_with("0.000") || s_trimmed == "0" || s_trimmed.starts_with("0.0"),
427 "sin(pi) = {s}"
428 );
429 }
430
431 #[test]
432 fn cos_of_pi_is_near_neg_one() {
433 let pi = compute_pi(40);
434 let result = cos(&pi, 20).expect("ok");
435 let s = result.to_string();
436 assert!(
437 s.starts_with("-1") || s.starts_with("-0.9999"),
438 "cos(pi) = {s}"
439 );
440 }
441
442 #[test]
443 fn sinh_of_zero() {
444 let x = make_dbig("0.0").expect("ok");
445 let result = sinh(&x, 30).expect("ok");
446 let s = result.to_string();
447 let s_trimmed = s.trim_start_matches('-');
448 assert!(s_trimmed.starts_with("0"), "sinh(0) = {s}");
449 }
450
451 #[test]
452 fn cosh_of_zero() {
453 let x = make_dbig("0.0").expect("ok");
454 let result = cosh(&x, 30).expect("ok");
455 let s = result.to_string();
456 assert!(
457 s.starts_with("1.000") || s.starts_with("1"),
458 "cosh(0) = {s}"
459 );
460 }
461
462 #[test]
463 fn tanh_of_zero() {
464 let x = make_dbig("0.0").expect("ok");
465 let result = tanh(&x, 30).expect("ok");
466 let s = result.to_string();
467 let s_trimmed = s.trim_start_matches('-');
468 assert!(s_trimmed.starts_with("0"), "tanh(0) = {s}");
469 }
470
471 #[test]
472 fn atan_of_zero() {
473 let x = make_dbig("0.0").expect("ok");
474 let result = atan(&x, 30).expect("ok");
475 assert_eq!(result, make_dbig("0.0").expect("ok"));
476 }
477
478 #[test]
479 fn atan_of_one_is_pi_over_4() {
480 let x = make_dbig("1.0").expect("ok");
481 let result = atan(&x, 30).expect("ok");
482 let s = result.to_string();
483 assert!(
484 s.starts_with("0.7853981"),
485 "atan(1) = {s}, expected ~0.7853981..."
486 );
487 }
488
489 #[test]
490 fn atan2_positive_x() {
491 let y = make_dbig("1.0").expect("ok");
492 let x = make_dbig("1.0").expect("ok");
493 let result = atan2(&y, &x, 20).expect("ok");
494 let s = result.to_string();
495 assert!(s.starts_with("0.785"), "atan2(1,1) = {s}");
496 }
497
498 #[test]
499 fn precision_zero_errors_trig() {
500 let x = make_dbig("1.0").expect("ok");
501 assert!(sin(&x, 0).is_err());
502 assert!(cos(&x, 0).is_err());
503 assert!(tan(&x, 0).is_err());
504 assert!(atan(&x, 0).is_err());
505 assert!(sinh(&x, 0).is_err());
506 assert!(cosh(&x, 0).is_err());
507 assert!(tanh(&x, 0).is_err());
508 }
509
510 #[test]
511 fn sin_cos_identity() {
512 let x = make_dbig("0.7").expect("ok");
514 let prec = 30;
515 let s = sin(&x, prec).expect("ok");
516 let c = cos(&x, prec).expect("ok");
517 let sum = &(&s * &s) + &(&c * &c);
518 let s_str = sum.to_string();
519 assert!(
521 s_str.starts_with("0.9999999999999999999999999")
522 || s_str.starts_with("1.000000000000000000000000"),
523 "sin^2(0.7) + cos^2(0.7) = {s_str}"
524 );
525 }
526
527 #[test]
528 fn sin_of_known_value() {
529 let x = make_dbig("0.7").expect("ok");
531 let result = sin(&x, 25).expect("ok");
532 let s = result.to_string();
533 assert!(
534 s.starts_with("0.644217687237691053672614"),
535 "sin(0.7) = {s}"
536 );
537 }
538
539 #[test]
540 fn cos_of_known_value() {
541 let x = make_dbig("0.7").expect("ok");
543 let result = cos(&x, 25).expect("ok");
544 let s = result.to_string();
545 assert!(
546 s.starts_with("0.764842187284488426255859"),
547 "cos(0.7) = {s}"
548 );
549 }
550}