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 + 20;
96 let one = dbig_at_precision("1.0", guard_prec);
97 let neg_one = dbig_at_precision("-1.0", guard_prec);
98
99 let x_ext = extend_precision(x, guard_prec);
103
104 let is_negative = x_ext < zero;
105 let abs_x = if is_negative {
106 &x_ext * &neg_one
107 } else {
108 x_ext.clone()
109 };
110
111 let result = if abs_x > one {
112 let pi = compute_pi(guard_prec);
114 let half_pi = &pi / &dbig_at_precision("2.0", guard_prec);
115 let recip = &one / &abs_x;
116 let atan_recip = atan_small(&recip, guard_prec);
117 &half_pi - &atan_recip
118 } else {
119 atan_small(&abs_x, guard_prec)
120 };
121
122 let final_result = if is_negative {
123 &result * &neg_one
124 } else {
125 result
126 };
127 Ok(truncate_to_precision(final_result, precision))
128}
129
130pub fn atan2(y: &DBig, x: &DBig, precision: usize) -> OxiNumResult<DBig> {
135 validate_precision(precision)?;
136 let zero = make_dbig("0.0")?;
137 let guard_prec = precision + 20;
138 let pi = compute_pi(guard_prec);
139
140 let y_ext = extend_precision(y, guard_prec);
144 let x_ext = extend_precision(x, guard_prec);
145
146 if *x > zero {
147 let ratio = &y_ext / &x_ext;
148 atan(&ratio, precision)
149 } else if *x < zero && *y >= zero {
150 let ratio = &y_ext / &x_ext;
151 let a = atan(&ratio, guard_prec)?;
152 Ok(truncate_to_precision(&a + &pi, precision))
153 } else if *x < zero && *y < zero {
154 let ratio = &y_ext / &x_ext;
155 let a = atan(&ratio, guard_prec)?;
156 Ok(truncate_to_precision(&a - &pi, precision))
157 } else if *x == zero && *y > zero {
158 let half_pi = &pi / &make_dbig("2.0")?;
159 Ok(truncate_to_precision(half_pi, precision))
160 } else if *x == zero && *y < zero {
161 let neg_half_pi = &(&pi / &make_dbig("2.0")?) * &make_dbig("-1.0")?;
162 Ok(truncate_to_precision(neg_half_pi, precision))
163 } else {
164 Ok(zero)
166 }
167}
168
169pub fn sinh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
175 validate_precision(precision)?;
176 let guard_prec = precision + 10;
177 let exp_x = exp(x, guard_prec)?;
178 let neg_x = x * &make_dbig("-1.0")?;
179 let exp_neg_x = exp(&neg_x, guard_prec)?;
180 let diff = &exp_x - &exp_neg_x;
181 let result = &diff / &make_dbig("2.0")?;
182 Ok(truncate_to_precision(result, precision))
183}
184
185pub fn cosh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
187 validate_precision(precision)?;
188 let guard_prec = precision + 10;
189 let exp_x = exp(x, guard_prec)?;
190 let neg_x = x * &make_dbig("-1.0")?;
191 let exp_neg_x = exp(&neg_x, guard_prec)?;
192 let sum = &exp_x + &exp_neg_x;
193 let result = &sum / &make_dbig("2.0")?;
194 Ok(truncate_to_precision(result, precision))
195}
196
197pub fn tanh(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
199 validate_precision(precision)?;
200 let guard_prec = precision + 10;
201 let s = sinh(x, guard_prec)?;
202 let c = cosh(x, guard_prec)?;
203 let result = &s / &c;
204 Ok(truncate_to_precision(result, precision))
205}
206
207fn sin_taylor(x: &DBig, precision: usize) -> DBig {
216 let x = extend_precision(x, precision);
217 let mut term = x.clone();
218 let mut sum = x.clone();
219 let x_sq = &x * &x;
220 let neg_one = dbig_at_precision("-1.0", precision);
221
222 for k in 1..=(precision as u32 + 20) {
223 let denom_a = dbig_u32_at_precision(2 * k, precision);
224 let denom_b = dbig_u32_at_precision(2 * k + 1, precision);
225 term = &term * &x_sq;
226 term = &term / &denom_a;
227 term = &term / &denom_b;
228 term = &term * &neg_one;
229 sum = &sum + &term;
230
231 if is_negligible(&term, precision) {
232 break;
233 }
234 }
235 sum
236}
237
238fn cos_taylor(x: &DBig, precision: usize) -> DBig {
243 let x = extend_precision(x, precision);
244 let one = dbig_at_precision("1.0", precision);
245 let mut term = one.clone();
246 let mut sum = one;
247 let x_sq = &x * &x;
248 let neg_one = dbig_at_precision("-1.0", precision);
249
250 for k in 1..=(precision as u32 + 20) {
251 let denom_a = dbig_u32_at_precision(2 * k - 1, precision);
252 let denom_b = dbig_u32_at_precision(2 * k, precision);
253 term = &term * &x_sq;
254 term = &term / &denom_a;
255 term = &term / &denom_b;
256 term = &term * &neg_one;
257 sum = &sum + &term;
258
259 if is_negligible(&term, precision) {
260 break;
261 }
262 }
263 sum
264}
265
266fn atan_small(x: &DBig, precision: usize) -> DBig {
272 let threshold = make_dbig("0.5").expect("valid literal");
276 let one = dbig_at_precision("1.0", precision);
277 let two = dbig_at_precision("2.0", precision);
278
279 let mut reduced = extend_precision(x, precision);
283 let mut halvings = 0u32;
284
285 while reduced > threshold {
286 let x_sq = &reduced * &reduced;
288 let inner = &one + &x_sq;
289 let guard_bits = precision * 4 + 20;
291 let fbig = convert_dbig_to_fbig(&inner, guard_bits);
292 let sqrt_val = dashu_base::SquareRoot::sqrt(&fbig);
293 let sqrt_dbig = fbig_to_dbig(&sqrt_val, precision);
294 reduced = &reduced / &(&one + &sqrt_dbig);
295 halvings += 1;
296
297 if halvings > 50 {
298 break; }
300 }
301
302 let mut result = atan_taylor_core(&reduced, precision);
303
304 for _ in 0..halvings {
306 result = &result * &two;
307 }
308
309 result
310}
311
312fn atan_taylor_core(x: &DBig, precision: usize) -> DBig {
318 let x = extend_precision(x, precision);
319 let mut power = x.clone();
320 let mut sum = x.clone();
321 let x_sq = &x * &x;
322 let neg_one = dbig_at_precision("-1.0", precision);
323 let mut sign = neg_one.clone();
324
325 for k in 1..=(precision as u32 * 3 + 50) {
326 power = &power * &x_sq;
327 let denom = dbig_u32_at_precision(2 * k + 1, precision);
328 let contrib = &power / &denom;
329 sum = &sum + &(&contrib * &sign);
330 sign = &sign * &neg_one;
331
332 if is_negligible(&contrib, precision) {
333 break;
334 }
335 }
336 sum
337}
338
339fn reduce_argument(x: &DBig, precision: usize) -> DBig {
345 let pi = compute_pi(precision.min(195) + 5);
346 let two = dbig_at_precision("2.0", precision + 5);
347 let two_pi = (&pi * &two).with_precision(precision + 5).value();
348 let zero = dbig_at_precision("0.0", precision + 5);
349
350 if two_pi == zero {
351 return x.clone();
352 }
353
354 let mut reduced = extend_precision(x, precision + 5);
356 if reduced > zero {
357 while reduced > two_pi {
358 reduced = (&reduced - &two_pi).with_precision(precision + 5).value();
359 }
360 } else {
361 while reduced < zero {
362 reduced = (&reduced + &two_pi).with_precision(precision + 5).value();
363 }
364 }
365 reduced
366}
367
368fn is_negligible(term: &DBig, precision: usize) -> bool {
370 let s = term.to_string();
371 let s = s.trim_start_matches('-');
372
373 if let Some(dot_pos) = s.find('.') {
374 let integer_part = &s[..dot_pos];
375 if integer_part == "0" {
376 let frac = &s[dot_pos + 1..];
377 let leading_zeros = frac.chars().take_while(|&c| c == '0').count();
378 return leading_zeros >= precision + 3;
379 }
380 }
381 s == "0" || s == "0.0"
382}
383
384fn make_dbig(s: &str) -> OxiNumResult<DBig> {
390 DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
391}
392
393fn extend_precision(value: &DBig, precision: usize) -> DBig {
396 value.clone().with_precision(precision).value()
397}
398
399fn dbig_at_precision(s: &str, precision: usize) -> DBig {
401 let v = DBig::from_str(s).expect("valid decimal literal");
402 v.with_precision(precision).value()
403}
404
405fn dbig_u32_at_precision(n: u32, precision: usize) -> DBig {
407 let v = DBig::from_str(&format!("{n}.0")).expect("integer.0 is always a valid decimal");
408 v.with_precision(precision).value()
409}
410
411fn validate_precision(precision: usize) -> OxiNumResult<()> {
412 if precision == 0 {
413 return Err(OxiNumError::Precision("precision must be > 0".into()));
414 }
415 Ok(())
416}
417
418#[cfg(test)]
423mod tests {
424 use super::*;
425
426 #[test]
427 fn sin_of_zero() {
428 let x = make_dbig("0.0").expect("ok");
429 let result = sin(&x, 30).expect("ok");
430 assert_eq!(result, make_dbig("0.0").expect("ok"));
431 }
432
433 #[test]
434 fn cos_of_zero() {
435 let result = cos(&make_dbig("0.0").expect("ok"), 30).expect("ok");
436 let s = result.to_string();
437 assert!(s.starts_with("1"), "cos(0) = {s}");
438 }
439
440 #[test]
441 fn sin_of_pi_is_near_zero() {
442 let pi = compute_pi(40);
443 let result = sin(&pi, 20).expect("ok");
444 let s = result.to_string();
445 let s_trimmed = s.trim_start_matches('-');
446 assert!(
447 s_trimmed.starts_with("0.000") || s_trimmed == "0" || s_trimmed.starts_with("0.0"),
448 "sin(pi) = {s}"
449 );
450 }
451
452 #[test]
453 fn cos_of_pi_is_near_neg_one() {
454 let pi = compute_pi(40);
455 let result = cos(&pi, 20).expect("ok");
456 let s = result.to_string();
457 assert!(
458 s.starts_with("-1") || s.starts_with("-0.9999"),
459 "cos(pi) = {s}"
460 );
461 }
462
463 #[test]
464 fn sinh_of_zero() {
465 let x = make_dbig("0.0").expect("ok");
466 let result = sinh(&x, 30).expect("ok");
467 let s = result.to_string();
468 let s_trimmed = s.trim_start_matches('-');
469 assert!(s_trimmed.starts_with("0"), "sinh(0) = {s}");
470 }
471
472 #[test]
473 fn cosh_of_zero() {
474 let x = make_dbig("0.0").expect("ok");
475 let result = cosh(&x, 30).expect("ok");
476 let s = result.to_string();
477 assert!(
478 s.starts_with("1.000") || s.starts_with("1"),
479 "cosh(0) = {s}"
480 );
481 }
482
483 #[test]
484 fn tanh_of_zero() {
485 let x = make_dbig("0.0").expect("ok");
486 let result = tanh(&x, 30).expect("ok");
487 let s = result.to_string();
488 let s_trimmed = s.trim_start_matches('-');
489 assert!(s_trimmed.starts_with("0"), "tanh(0) = {s}");
490 }
491
492 #[test]
493 fn atan_of_zero() {
494 let x = make_dbig("0.0").expect("ok");
495 let result = atan(&x, 30).expect("ok");
496 assert_eq!(result, make_dbig("0.0").expect("ok"));
497 }
498
499 #[test]
500 fn atan_of_one_is_pi_over_4() {
501 let x = make_dbig("1.0").expect("ok");
502 let result = atan(&x, 30).expect("ok");
503 let s = result.to_string();
504 assert!(
505 s.starts_with("0.7853981"),
506 "atan(1) = {s}, expected ~0.7853981..."
507 );
508 }
509
510 #[test]
511 fn atan2_positive_x() {
512 let y = make_dbig("1.0").expect("ok");
513 let x = make_dbig("1.0").expect("ok");
514 let result = atan2(&y, &x, 20).expect("ok");
515 let s = result.to_string();
516 assert!(s.starts_with("0.785"), "atan2(1,1) = {s}");
517 }
518
519 #[test]
520 fn precision_zero_errors_trig() {
521 let x = make_dbig("1.0").expect("ok");
522 assert!(sin(&x, 0).is_err());
523 assert!(cos(&x, 0).is_err());
524 assert!(tan(&x, 0).is_err());
525 assert!(atan(&x, 0).is_err());
526 assert!(sinh(&x, 0).is_err());
527 assert!(cosh(&x, 0).is_err());
528 assert!(tanh(&x, 0).is_err());
529 }
530
531 #[test]
532 fn sin_cos_identity() {
533 let x = make_dbig("0.7").expect("ok");
535 let prec = 30;
536 let s = sin(&x, prec).expect("ok");
537 let c = cos(&x, prec).expect("ok");
538 let sum = &(&s * &s) + &(&c * &c);
539 let s_str = sum.to_string();
540 assert!(
542 s_str.starts_with("0.9999999999999999999999999")
543 || s_str.starts_with("1.000000000000000000000000"),
544 "sin^2(0.7) + cos^2(0.7) = {s_str}"
545 );
546 }
547
548 #[test]
549 fn sin_of_known_value() {
550 let x = make_dbig("0.7").expect("ok");
552 let result = sin(&x, 25).expect("ok");
553 let s = result.to_string();
554 assert!(
555 s.starts_with("0.644217687237691053672614"),
556 "sin(0.7) = {s}"
557 );
558 }
559
560 #[test]
561 fn cos_of_known_value() {
562 let x = make_dbig("0.7").expect("ok");
564 let result = cos(&x, 25).expect("ok");
565 let s = result.to_string();
566 assert!(
567 s.starts_with("0.764842187284488426255859"),
568 "cos(0.7) = {s}"
569 );
570 }
571}