1use crate::elementary::{exp, ln, sqrt, truncate_to_precision};
30use crate::trig::{cos, sin};
31use crate::{DBig, OxiNumError, OxiNumResult};
32use std::str::FromStr;
33
34const EULER_GAMMA_200: &str =
40 "0.57721566490153286060651209008240243104215933593992359880576723488486772677766467\
41 09369596694504673425296068890403823946951285765500044721133652219082019609773798\
42 42060299066541920";
43
44const CATALAN_200: &str =
46 "0.91596559417721901505460351493238411077414937428167213426649811962176301977625476\
47 94709053505388297009241232177413908000993617044680583060800913695168700767543730\
48 49861741618979838";
49
50pub fn euler_gamma(precision: usize) -> DBig {
64 parse_at_precision(EULER_GAMMA_200, precision)
65}
66
67pub fn catalan(precision: usize) -> DBig {
77 parse_at_precision(CATALAN_200, precision)
78}
79
80pub fn free_cache() {
86 }
88
89pub fn gamma(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
103 validate_precision(precision)?;
104 let guard = precision + 20;
105 gamma_impl(x, guard, precision)
106}
107
108fn gamma_impl(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
109 let zero = make_dbig("0.0")?;
110 let one = make_dbig("1.0")?;
111
112 if *x == zero {
113 return Err(OxiNumError::Domain("Gamma(0) is undefined (pole)".into()));
114 }
115
116 if is_negative(x) && is_integer_value(x) {
119 return Err(OxiNumError::Domain(
120 "Gamma undefined at negative integers (poles)".into(),
121 ));
122 }
123
124 if is_positive(x) {
125 if to_f64_approx(x) > 20.0 {
126 stirling_gamma(x, guard, output_prec)
127 } else {
128 lanczos_gamma(x, guard, output_prec)
129 }
130 } else {
131 let pi = crate::constants::compute_pi(guard);
133 let pi_ext = extend(pi, guard);
134 let x_ext = extend(x.clone(), guard);
135 let pi_x = &pi_ext * &x_ext;
136 let sin_pi_x = sin(&pi_x, guard)?;
137 if is_approx_zero(&sin_pi_x) {
138 return Err(OxiNumError::Domain(
139 "Gamma undefined at negative integers (poles)".into(),
140 ));
141 }
142 let one_minus_x = &extend(one, guard) - &x_ext;
143 let gamma_pos = gamma_impl(&one_minus_x, guard, guard)?;
144 let result = &pi_ext / &(&sin_pi_x * &gamma_pos);
145 Ok(truncate_to_precision(result, output_prec))
146 }
147}
148
149fn lanczos_gamma(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
151 const LANCZOS_G: f64 = 7.0;
153 const LANCZOS_COEFFS: &[f64] = &[
154 0.999_999_999_999_809_9,
155 676.520_368_121_885_1,
156 -1_259.139_216_722_403,
157 771.323_428_777_653_1,
158 -176.615_029_162_140_6,
159 12.507_343_278_686_905,
160 -0.138_571_095_265_720_1,
161 9.984_369_578_019_572e-6,
162 1.505_632_735_149_311_6e-7,
163 ];
164
165 let x_ext = extend(x.clone(), guard);
166 let two_pi = dbig_f64(2.0 * std::f64::consts::PI, guard);
167 let sqrt_2pi = sqrt(&two_pi, guard)?;
168
169 let mut ag = dbig_f64(LANCZOS_COEFFS[0], guard);
171 for (i, &c) in LANCZOS_COEFFS[1..].iter().enumerate() {
172 let denom = &x_ext + &dbig_f64(i as f64 + 1.0, guard);
173 ag = &ag + &(&dbig_f64(c, guard) / &denom);
174 }
175
176 let tmp = &x_ext + &dbig_f64(LANCZOS_G + 0.5, guard);
178
179 let x_plus_half = &x_ext + &dbig_f64(0.5, guard);
182 let ln_tmp = ln(&tmp, guard)?;
183 let log_part = &(&x_plus_half * &ln_tmp) - &tmp;
184 let exp_part = exp(&log_part, guard)?;
185
186 let result = &(&sqrt_2pi * &ag) * &exp_part;
187 let final_result = &result / &x_ext;
189 Ok(truncate_to_precision(final_result, output_prec))
190}
191
192fn stirling_gamma(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
194 let x_ext = extend(x.clone(), guard);
195 let two_pi = dbig_f64(2.0 * std::f64::consts::PI, guard);
196 let sqrt_2pi = sqrt(&two_pi, guard)?;
197
198 let sqrt_2pi_over_x = &sqrt_2pi / &sqrt(&x_ext, guard)?;
200
201 let ln_x = ln(&x_ext, guard)?;
203 let log_part = &(&x_ext * &ln_x) - &x_ext;
204 let exp_part = exp(&log_part, guard)?;
205
206 let x2 = &x_ext * &x_ext;
208 let x3 = &x2 * &x_ext;
209 let x4 = &x2 * &x2;
210
211 let c1 = &dbig_f64(1.0, guard) / &(&dbig_f64(12.0, guard) * &x_ext);
212 let c2 = &dbig_f64(1.0, guard) / &(&dbig_f64(288.0, guard) * &x2);
213 let c3 = &(&dbig_f64(139.0, guard) / &(&dbig_f64(51840.0, guard) * &x3));
214 let c4 = &(&dbig_f64(571.0, guard) / &(&dbig_f64(2488320.0, guard) * &x4));
215
216 let correction = &(&(&dbig_f64(1.0, guard) + &c1) + &c2) - (&(&c3.clone() + &c4.clone()));
217
218 let result = &(&sqrt_2pi_over_x * &exp_part) * &correction;
219 Ok(truncate_to_precision(result, output_prec))
220}
221
222pub fn ln_gamma(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
232 validate_precision(precision)?;
233 let zero = make_dbig("0.0")?;
234 if *x <= zero {
235 return Err(OxiNumError::Domain("ln_gamma undefined for x <= 0".into()));
236 }
237
238 let guard = precision + 20;
239 let x_f = to_f64_approx(x);
240
241 if x_f > 10.0 {
242 stirling_ln_gamma(x, guard, precision)
243 } else {
244 let g = gamma_impl(x, guard, guard)?;
246 let lg = ln(&g, guard)?;
247 Ok(truncate_to_precision(lg, precision))
248 }
249}
250
251fn stirling_ln_gamma(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
253 let x_ext = extend(x.clone(), guard);
254 let two_pi = dbig_f64(2.0 * std::f64::consts::PI, guard);
255 let ln_2pi = ln(&two_pi, guard)?;
256 let ln_x = ln(&x_ext, guard)?;
257
258 let half = dbig_f64(0.5, guard);
260 let x_minus_half = &x_ext - ½
261
262 let x2 = &x_ext * &x_ext;
263 let x3 = &x2 * &x_ext;
264 let x5 = &x3 * &x2;
265 let x7 = &x5 * &x2;
266
267 let base = &(&x_minus_half * &ln_x) - &x_ext;
268 let term0 = &ln_2pi / &dbig_f64(2.0, guard);
269 let term1 = &dbig_f64(1.0, guard) / &(&dbig_f64(12.0, guard) * &x_ext);
270 let term2 = &dbig_f64(1.0, guard) / &(&dbig_f64(360.0, guard) * &x3);
271 let term3 = &dbig_f64(1.0, guard) / &(&dbig_f64(1260.0, guard) * &x5);
272 let term4 = &dbig_f64(1.0, guard) / &(&dbig_f64(1680.0, guard) * &x7);
273
274 let result =
275 &(&(&base + &term0) + &term1) - &(&(&term2.clone() - &term3.clone()) + &term4.clone());
276 Ok(truncate_to_precision(result, output_prec))
277}
278
279pub fn digamma(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
293 validate_precision(precision)?;
294 let zero = make_dbig("0.0")?;
295 if *x == zero {
296 return Err(OxiNumError::Domain("digamma(0) is undefined (pole)".into()));
297 }
298 if is_negative(x) && is_integer_value(x) {
299 return Err(OxiNumError::Domain(
300 "digamma undefined at negative integers (poles)".into(),
301 ));
302 }
303
304 let guard = precision + 30;
305 digamma_impl(x, guard, precision)
306}
307
308fn digamma_impl(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
309 let zero = make_dbig("0.0")?;
310 let one = dbig_f64(1.0, guard);
311
312 if is_negative(x) || to_f64_approx(x) < 1.0 {
314 let pi = extend(crate::constants::compute_pi(guard), guard);
315 let x_ext = extend(x.clone(), guard);
316 let pi_x = &pi * &x_ext;
317 let sin_pix = sin(&pi_x, guard)?;
318 let cos_pix = cos(&pi_x, guard)?;
319 if is_approx_zero(&sin_pix) {
320 return Err(OxiNumError::Domain(
321 "digamma undefined at non-positive integers".into(),
322 ));
323 }
324 let cot_pi_x = &cos_pix / &sin_pix;
325 let pi_cot = &pi * &cot_pi_x;
326
327 let one_minus_x = &one - &x_ext;
328 let psi_1mx = digamma_impl(&one_minus_x, guard, guard)?;
329 let result = &psi_1mx - &pi_cot;
330 return Ok(truncate_to_precision(result, output_prec));
331 }
332
333 let x_ext = extend(x.clone(), guard);
336 let mut shift_sum = extend(zero, guard);
337 let mut curr_x = x_ext.clone();
338
339 while to_f64_approx(&curr_x) < 8.0 {
340 shift_sum = &shift_sum + &(&one / &curr_x);
341 curr_x = &curr_x + &one;
342 }
343
344 let ln_cx = ln(&curr_x, guard)?;
347 let cx2 = &curr_x * &curr_x;
348 let cx4 = &cx2 * &cx2;
349 let cx6 = &cx4 * &cx2;
350 let cx8 = &cx4 * &cx4;
351
352 let t1 = &dbig_f64(1.0, guard) / &(&dbig_f64(2.0, guard) * &curr_x);
353 let t2 = &dbig_f64(1.0, guard) / &(&dbig_f64(12.0, guard) * &cx2);
355 let t3 = &dbig_f64(1.0, guard) / &(&dbig_f64(120.0, guard) * &cx4);
357 let t4 = &dbig_f64(1.0, guard) / &(&dbig_f64(252.0, guard) * &cx6);
359 let t5 = &dbig_f64(1.0, guard) / &(&dbig_f64(240.0, guard) * &cx8);
361
362 let asymp = &(&(&ln_cx - &t1) - &t2) + &(&(&t3 - &t4) + &t5);
364
365 let result = &asymp - &shift_sum;
366 Ok(truncate_to_precision(result, output_prec))
367}
368
369pub fn erf(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
377 validate_precision(precision)?;
378 let zero = make_dbig("0.0")?;
379 if *x == zero {
380 return Ok(zero);
381 }
382
383 let guard = precision + 20;
384 let abs_x_f = to_f64_approx(x).abs();
385
386 if abs_x_f <= 2.0 {
387 erf_series(x, guard, precision)
388 } else {
389 let abs_x = abs_dbig(x, guard);
391 let erfc_val = erfc_asymptotic(&abs_x, guard, guard)?;
392 let one = dbig_f64(1.0, guard);
393 let result = if is_positive(x) {
394 &one - &erfc_val
395 } else {
396 &erfc_val - &one
397 };
398 Ok(truncate_to_precision(result, precision))
399 }
400}
401
402fn erf_series(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
404 let pi = extend(crate::constants::compute_pi(guard), guard);
405 let sqrt_pi = sqrt(&pi, guard)?;
406 let two_over_sqrt_pi = &dbig_f64(2.0, guard) / &sqrt_pi;
407
408 let x_ext = extend(x.clone(), guard);
409 let x2 = &x_ext * &x_ext;
410 let neg_one = dbig_f64(-1.0, guard);
411
412 let mut sum = x_ext.clone();
413 let mut term = x_ext.clone();
414
415 for k in 1u32..=(guard as u32 * 3 + 50) {
416 let neg_x2_over_k = &(&x2 * &neg_one) / &dbig_f64(k as f64, guard);
418 term = &term * &neg_x2_over_k;
419 let contrib = &term / &dbig_f64(2.0 * k as f64 + 1.0, guard);
421 sum = &sum + &contrib;
422
423 if is_negligible(&contrib, output_prec) {
424 break;
425 }
426 }
427
428 let result = &two_over_sqrt_pi * ∑
429 Ok(truncate_to_precision(result, output_prec))
430}
431
432fn erfc_asymptotic(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
435 let pi = extend(crate::constants::compute_pi(guard), guard);
436 let sqrt_pi = sqrt(&pi, guard)?;
437 let x_ext = extend(x.clone(), guard);
438 let x2 = &x_ext * &x_ext;
439 let neg_x2 = &x2 * &dbig_f64(-1.0, guard);
440 let exp_neg_x2 = exp(&neg_x2, guard)?;
441
442 let mut sum = dbig_f64(1.0, guard);
443 let mut term = dbig_f64(1.0, guard);
444
445 for k in 1u32..50 {
446 let two_x2 = &dbig_f64(2.0, guard) * &x2;
448 let factor = &dbig_f64(-((2 * k - 1) as f64), guard) / &two_x2;
449 term = &term * &factor;
450 sum = &sum + &term;
451
452 if is_negligible(&term, output_prec) {
453 break;
454 }
455 if to_f64_approx(&abs_dbig(&term, guard)) > to_f64_approx(&abs_dbig(&sum, guard)) {
457 break;
458 }
459 }
460
461 let denom = &x_ext * &sqrt_pi;
463 let result = &(&exp_neg_x2 * &sum) / &denom;
464 Ok(truncate_to_precision(result, output_prec))
465}
466
467pub fn erfc(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
475 validate_precision(precision)?;
476 let zero = make_dbig("0.0")?;
477 if *x == zero {
478 return Ok(dbig_f64(1.0, precision));
479 }
480
481 let guard = precision + 20;
482 let abs_x_f = to_f64_approx(x).abs();
483
484 if abs_x_f <= 2.0 {
485 let erf_val = erf_series(x, guard, guard)?;
486 let one = dbig_f64(1.0, guard);
487 let result = &one - &erf_val;
488 Ok(truncate_to_precision(result, precision))
489 } else {
490 let abs_x = abs_dbig(x, guard);
491 let erfc_val = erfc_asymptotic(&abs_x, guard, guard)?;
492 let result = if is_positive(x) {
493 truncate_to_precision(erfc_val, precision)
494 } else {
495 let two = dbig_f64(2.0, guard);
497 truncate_to_precision(&two - &erfc_val, precision)
498 };
499 Ok(result)
500 }
501}
502
503pub fn bessel_j0(x: &DBig, precision: usize) -> OxiNumResult<DBig> {
513 validate_precision(precision)?;
514 let zero = make_dbig("0.0")?;
515 if *x == zero {
516 return Ok(dbig_f64(1.0, precision));
517 }
518
519 let guard = precision + 20;
520 let abs_x_f = to_f64_approx(x).abs();
521
522 if abs_x_f <= 12.0 {
523 bessel_j0_series(x, guard, precision)
524 } else {
525 bessel_j0_asymptotic(x, guard, precision)
526 }
527}
528
529fn bessel_j0_series(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
531 let x_ext = extend(x.clone(), guard);
532 let x_half = &x_ext / &dbig_f64(2.0, guard);
534 let x_half_sq = &x_half * &x_half;
536
537 let mut sum = dbig_f64(1.0, guard);
538 let mut term = dbig_f64(1.0, guard);
539
540 for k in 1u32..=(guard as u32 * 2 + 60) {
541 let k_sq = dbig_f64((k as f64) * (k as f64), guard);
543 let neg_xh2 = &x_half_sq * &dbig_f64(-1.0, guard);
544 term = &term * &(&neg_xh2 / &k_sq);
545 sum = &sum + &term;
546
547 if is_negligible(&term, output_prec) {
548 break;
549 }
550 }
551
552 Ok(truncate_to_precision(sum, output_prec))
553}
554
555fn bessel_j0_asymptotic(x: &DBig, guard: usize, output_prec: usize) -> OxiNumResult<DBig> {
559 let x_ext = extend(x.clone(), guard);
560 let pi = extend(crate::constants::compute_pi(guard), guard);
561
562 let pi_x = &pi * &x_ext;
564 let two_over_pi_x = &dbig_f64(2.0, guard) / &pi_x;
565 let sqrt_prefactor = sqrt(&two_over_pi_x, guard)?;
566
567 let pi_over_4 = &pi / &dbig_f64(4.0, guard);
569 let phase = &x_ext - &pi_over_4;
570
571 let x2 = &x_ext * &x_ext;
575 let x4 = &x2 * &x2;
576
577 let p0_t1 = &dbig_f64(9.0, guard) / &(&dbig_f64(128.0, guard) * &x2);
579 let p0_t2 = &dbig_f64(3675.0, guard) / &(&dbig_f64(32768.0, guard) * &x4);
580 let p0 = &(&dbig_f64(1.0, guard) - &p0_t1) + &p0_t2;
581
582 let x3 = &x2 * &x_ext;
584 let q0_t1 = &dbig_f64(1.0, guard) / &(&dbig_f64(8.0, guard) * &x_ext);
585 let q0_t2 = &dbig_f64(75.0, guard) / &(&dbig_f64(1024.0, guard) * &x3);
586 let q0 = &q0_t2 - &q0_t1;
587
588 let cos_phase = cos(&phase, guard)?;
589 let sin_phase = sin(&phase, guard)?;
590
591 let result = &sqrt_prefactor * &(&(&p0 * &cos_phase) - &(&q0 * &sin_phase));
593 Ok(truncate_to_precision(result, output_prec))
594}
595
596fn validate_precision(precision: usize) -> OxiNumResult<()> {
601 if precision == 0 {
602 return Err(OxiNumError::Precision("precision must be > 0".into()));
603 }
604 Ok(())
605}
606
607fn make_dbig(s: &str) -> OxiNumResult<DBig> {
608 DBig::from_str(s).map_err(|e| OxiNumError::Parse(format!("{e}").into()))
609}
610
611fn extend(v: DBig, precision: usize) -> DBig {
613 v.with_precision(precision).value()
614}
615
616fn dbig_f64(v: f64, precision: usize) -> DBig {
618 let s = format!("{:.prec$e}", v, prec = precision + 5);
619 DBig::from_str(&s)
620 .unwrap_or_else(|_| DBig::from_str(&format!("{v}")).expect("f64 to DBig should not fail"))
621 .with_precision(precision)
622 .value()
623}
624
625fn is_approx_zero(v: &DBig) -> bool {
627 let s = v.to_string().trim_start_matches('-').to_string();
628 s == "0" || s == "0.0" || {
629 if let Some(dot) = s.find('.') {
631 let int_part = &s[..dot];
632 let frac = &s[dot + 1..];
633 let leading_zeros = frac.chars().take_while(|&c| c == '0').count();
634 int_part == "0" && leading_zeros >= 15
635 } else {
636 false
637 }
638 }
639}
640
641fn is_negligible(term: &DBig, precision: usize) -> bool {
643 let s = term.to_string();
644 let s = s.trim_start_matches('-');
645 if let Some(dot_pos) = s.find('.') {
646 let integer_part = &s[..dot_pos];
647 if integer_part == "0" {
648 let frac = &s[dot_pos + 1..];
649 let leading_zeros = frac.chars().take_while(|&c| c == '0').count();
650 return leading_zeros >= precision + 3;
651 }
652 }
653 s == "0" || s == "0.0"
654}
655
656fn to_f64_approx(v: &DBig) -> f64 {
658 let s = v.to_string();
659 s.parse::<f64>().unwrap_or(0.0)
660}
661
662fn is_positive(v: &DBig) -> bool {
664 !v.to_string().starts_with('-') && !is_approx_zero(v)
665}
666
667fn is_negative(v: &DBig) -> bool {
669 v.to_string().starts_with('-') && !is_approx_zero(v)
670}
671
672fn is_integer_value(v: &DBig) -> bool {
674 let s = v.to_string().trim_start_matches('-').to_string();
675 if let Some(dot) = s.find('.') {
676 let frac = &s[dot + 1..];
677 frac.chars().all(|c| c == '0')
678 } else {
679 !s.is_empty()
681 }
682}
683
684fn abs_dbig(v: &DBig, precision: usize) -> DBig {
686 let s = v.to_string();
687 if let Some(stripped) = s.strip_prefix('-') {
688 DBig::from_str(stripped)
689 .unwrap_or_else(|_| v.clone())
690 .with_precision(precision)
691 .value()
692 } else {
693 v.clone().with_precision(precision).value()
694 }
695}
696
697fn parse_at_precision(src: &str, precision: usize) -> DBig {
699 use crate::elementary::truncate_decimal_str;
700 let prec = precision.clamp(1, 200);
701 let truncated = truncate_decimal_str(src, prec);
702 DBig::from_str(&truncated).expect("pre-stored constant is a valid decimal literal")
703}
704
705#[cfg(test)]
710mod tests {
711 use super::*;
712
713 fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
714 (a - b).abs() <= tol * b.abs().max(1e-300)
715 }
716
717 #[test]
718 fn euler_gamma_value() {
719 let g = euler_gamma(30);
720 let s = g.to_string();
721 assert!(
722 s.starts_with("0.577215664901532860606512"),
723 "euler_gamma = {s}"
724 );
725 }
726
727 #[test]
728 fn catalan_value() {
729 let g = catalan(30);
730 let s = g.to_string();
731 assert!(s.starts_with("0.915965594177219015054603"), "catalan = {s}");
732 }
733
734 #[test]
735 fn gamma_one() {
736 let x = DBig::from_str("1.0").expect("ok");
737 let g = gamma(&x, 30).expect("gamma(1) ok");
738 let v = to_f64_approx(&g);
739 assert!(approx_eq(v, 1.0, 1e-10), "gamma(1) = {v}");
740 }
741
742 #[test]
743 fn gamma_half_is_sqrt_pi() {
744 let x = DBig::from_str("0.5").expect("ok");
745 let g = gamma(&x, 30).expect("gamma(0.5) ok");
746 let v = to_f64_approx(&g);
747 let expected = std::f64::consts::PI.sqrt();
748 assert!(
749 approx_eq(v, expected, 1e-10),
750 "gamma(0.5) = {v}, expected {expected}"
751 );
752 }
753
754 #[test]
755 fn gamma_five_is_24() {
756 let x = DBig::from_str("5.0").expect("ok");
757 let g = gamma(&x, 30).expect("gamma(5) ok");
758 let v = to_f64_approx(&g);
759 assert!(approx_eq(v, 24.0, 1e-10), "gamma(5) = {v}");
760 }
761
762 #[test]
763 fn gamma_pole_at_zero() {
764 let x = DBig::from_str("0.0").expect("ok");
765 assert!(gamma(&x, 20).is_err());
766 }
767
768 #[test]
769 fn gamma_pole_at_neg_int() {
770 let x = DBig::from_str("-3.0").expect("ok");
771 assert!(gamma(&x, 20).is_err());
772 }
773
774 #[test]
775 fn ln_gamma_one_is_zero() {
776 let x = DBig::from_str("1.0").expect("ok");
777 let lg = ln_gamma(&x, 30).expect("ok");
778 let v = to_f64_approx(&lg).abs();
779 assert!(v < 1e-8, "ln_gamma(1) = {v}");
780 }
781
782 #[test]
783 fn ln_gamma_large() {
784 let x = DBig::from_str("100.0").expect("ok");
785 let lg = ln_gamma(&x, 30).expect("ok");
786 let v = to_f64_approx(&lg);
787 assert!((v - 359.134_f64).abs() < 0.001, "ln_gamma(100) = {v}");
789 }
790
791 #[test]
792 fn digamma_one_is_neg_euler() {
793 let x = DBig::from_str("1.0").expect("ok");
795 let d = digamma(&x, 30).expect("ok");
796 let v = to_f64_approx(&d);
797 let expected = -0.5772156649_f64;
798 assert!(
799 (v - expected).abs() < 1e-8,
800 "digamma(1) = {v}, expected {expected}"
801 );
802 }
803
804 #[test]
805 fn digamma_pole_at_zero() {
806 let x = DBig::from_str("0.0").expect("ok");
807 assert!(digamma(&x, 20).is_err());
808 }
809
810 #[test]
811 fn erf_zero() {
812 let x = DBig::from_str("0.0").expect("ok");
813 let v = erf(&x, 20).expect("ok");
814 let s = v.to_string();
815 let clean = s.trim_start_matches('-');
816 assert!(
817 clean == "0" || clean == "0.0" || clean.starts_with("0.000000"),
818 "erf(0) should be zero, got {s}"
819 );
820 }
821
822 #[test]
823 fn erf_one() {
824 let x = DBig::from_str("1.0").expect("ok");
825 let v = erf(&x, 20).expect("ok");
826 let f = to_f64_approx(&v);
827 assert!((f - 0.8427007929_f64).abs() < 1e-8, "erf(1) = {f}");
829 }
830
831 #[test]
832 fn erf_symmetry() {
833 let x = DBig::from_str("0.8").expect("ok");
834 let xn = DBig::from_str("-0.8").expect("ok");
835 let ep = erf(&x, 25).expect("ok");
836 let en = erf(&xn, 25).expect("ok");
837 let fp = to_f64_approx(&ep);
838 let fn_ = to_f64_approx(&en);
839 assert!((fp + fn_).abs() < 1e-10, "erf(x) + erf(-x) = {}", fp + fn_);
840 }
841
842 #[test]
843 fn erfc_zero() {
844 let x = DBig::from_str("0.0").expect("ok");
845 let v = erfc(&x, 20).expect("ok");
846 let f = to_f64_approx(&v);
847 assert!((f - 1.0).abs() < 1e-10, "erfc(0) = {f}");
848 }
849
850 #[test]
851 fn erf_plus_erfc_is_one() {
852 let x = DBig::from_str("1.5").expect("ok");
853 let e = erf(&x, 20).expect("ok");
854 let ec = erfc(&x, 20).expect("ok");
855 let sum = to_f64_approx(&e) + to_f64_approx(&ec);
856 assert!((sum - 1.0).abs() < 1e-10, "erf(1.5)+erfc(1.5) = {sum}");
857 }
858
859 #[test]
860 fn bessel_j0_zero() {
861 let x = DBig::from_str("0.0").expect("ok");
862 let v = bessel_j0(&x, 20).expect("ok");
863 let f = to_f64_approx(&v);
864 assert!((f - 1.0).abs() < 1e-10, "J0(0) = {f}");
865 }
866
867 #[test]
868 fn bessel_j0_first_zero_approx() {
869 let x = DBig::from_str("2.4048255577").expect("ok");
872 let v = bessel_j0(&x, 20).expect("ok");
873 let f = to_f64_approx(&v).abs();
874 assert!(f < 1e-5, "J0(2.4048) ≈ 0, got {f}");
875 }
876
877 #[test]
878 fn free_cache_is_noop() {
879 free_cache();
881 }
882}