@@ -530,9 +530,7 @@ pub fn expr_to_bigfloat(
530530 }
531531 "Erfc" if args. len ( ) == 1 => {
532532 let x = expr_to_bigfloat ( & args[ 0 ] , bits, rm, cc) ?;
533- let erf_val = bigfloat_erf ( & x, bits, rm, cc) ;
534- let one = BigFloat :: from_i32 ( 1 , bits) ;
535- Ok ( one. sub ( & erf_val, bits, rm) )
533+ Ok ( bigfloat_erfc ( & x, bits, rm, cc) )
536534 }
537535 "ExpIntegralEi" if args. len ( ) == 1 => {
538536 let x = expr_to_bigfloat ( & args[ 0 ] , bits, rm, cc) ?;
@@ -2631,6 +2629,78 @@ pub fn manhattan_distance_ast(args: &[Expr]) -> Result<Expr, InterpreterError> {
26312629
26322630/// Compute the error function erf(x) using BigFloat arithmetic.
26332631/// Uses the Taylor series: erf(x) = (2/sqrt(π)) * Σ_{n=0}^{∞} (-1)^n * x^(2n+1) / (n! * (2n+1))
2632+ /// Compute erfc(x) for x > 0 using the continued fraction representation.
2633+ /// erfc(x) = exp(-x²) / (f * sqrt(π)) where f is computed via modified Lentz's method.
2634+ fn bigfloat_erfc_cf (
2635+ x : & astro_float:: BigFloat ,
2636+ bits : usize ,
2637+ rm : astro_float:: RoundingMode ,
2638+ cc : & mut astro_float:: Consts ,
2639+ ) -> astro_float:: BigFloat {
2640+ use astro_float:: BigFloat ;
2641+
2642+ // Use extra guard bits for intermediate computation
2643+ let work_bits = bits + 64 ;
2644+
2645+ let half = BigFloat :: from_i32 ( 1 , work_bits) . div (
2646+ & BigFloat :: from_i32 ( 2 , work_bits) ,
2647+ work_bits,
2648+ rm,
2649+ ) ;
2650+
2651+ // Modified Lentz's method for the continued fraction
2652+ // erfc(x) = (exp(-x²)/sqrt(π)) * 1/(x + 1/(2x + 2/(x + 3/(2x + ...))))
2653+ // Using: a_n = n * 0.5, b_n = x
2654+ let mut f = x. clone ( ) ;
2655+ let mut c = x. clone ( ) ;
2656+ let mut d = BigFloat :: from_i32 ( 0 , work_bits) ;
2657+
2658+ let max_iterations = work_bits * 2 + 200 ;
2659+ for n in 1 ..max_iterations {
2660+ // a_n = n * 0.5
2661+ let a_n = BigFloat :: from_i32 ( n as i32 , work_bits) . mul ( & half, work_bits, rm) ;
2662+
2663+ // d = x + a_n * d
2664+ d = x. add ( & a_n. mul ( & d, work_bits, rm) , work_bits, rm) ;
2665+ // Guard against zero
2666+ if d. is_zero ( ) {
2667+ d = BigFloat :: min_positive_normal ( work_bits) ;
2668+ }
2669+
2670+ // c = x + a_n / c
2671+ c = x. add ( & a_n. div ( & c, work_bits, rm) , work_bits, rm) ;
2672+ if c. is_zero ( ) {
2673+ c = BigFloat :: min_positive_normal ( work_bits) ;
2674+ }
2675+
2676+ // d = 1/d
2677+ d = BigFloat :: from_i32 ( 1 , work_bits) . div ( & d, work_bits, rm) ;
2678+ let delta = c. mul ( & d, work_bits, rm) ;
2679+ f = f. mul ( & delta, work_bits, rm) ;
2680+
2681+ // Check convergence: |delta - 1| is negligible
2682+ let one = BigFloat :: from_i32 ( 1 , work_bits) ;
2683+ let diff = delta. sub ( & one, work_bits, rm) . abs ( ) ;
2684+ if diff. is_zero ( ) {
2685+ break ;
2686+ }
2687+ if let Some ( diff_exp) = diff. exponent ( )
2688+ && diff_exp < -( work_bits as i32 )
2689+ {
2690+ break ;
2691+ }
2692+ }
2693+
2694+ // erfc(x) = exp(-x²) / (f * sqrt(π))
2695+ let x2 = x. mul ( x, work_bits, rm) ;
2696+ let neg_x2 = x2. neg ( ) ;
2697+ let exp_neg_x2 = neg_x2. exp ( work_bits, rm, cc) ;
2698+ let pi = cc. pi ( work_bits, rm) ;
2699+ let sqrt_pi = pi. sqrt ( work_bits, rm) ;
2700+ let denom = f. mul ( & sqrt_pi, work_bits, rm) ;
2701+ exp_neg_x2. div ( & denom, bits, rm)
2702+ }
2703+
26342704fn bigfloat_erf (
26352705 x : & astro_float:: BigFloat ,
26362706 bits : usize ,
@@ -2647,52 +2717,102 @@ fn bigfloat_erf(
26472717 let is_negative = x. is_negative ( ) ;
26482718 let x_abs = x. abs ( ) ;
26492719
2720+ // For large |x|, use the continued fraction for erfc(x) and compute erf = 1 - erfc.
2721+ // The Taylor series suffers from catastrophic cancellation for large arguments.
2722+ let four = BigFloat :: from_i32 ( 4 , bits) ;
2723+ if x_abs. cmp ( & four) == Some ( 1 ) {
2724+ // |x| > 4: use continued fraction
2725+ let erfc_val = bigfloat_erfc_cf ( & x_abs, bits, rm, cc) ;
2726+ let one = BigFloat :: from_i32 ( 1 , bits) ;
2727+ let result = one. sub ( & erfc_val, bits, rm) ;
2728+ return if is_negative { result. neg ( ) } else { result } ;
2729+ }
2730+
2731+ // For small |x| (≤ 4), use the Taylor series with extra guard bits to handle cancellation.
2732+ // With |x| ≤ 4, the peak term is ~exp(x²/2) ≈ exp(8) ≈ 2981, needing ~12 extra bits.
2733+ // We use 64 guard bits for safety.
2734+ let work_bits = bits + 64 ;
2735+
26502736 // Taylor series: term_0 = x, term_n = term_{n-1} * x^2 / n
26512737 // contribution_n = term_n / (2n+1), alternating sign
26522738 // sum = Σ (-1)^n * contribution_n
2653- //
2654- // Use the target precision for all computations to match Wolfram's
2655- // rounding behavior.
2656- let x2 = x_abs. mul ( & x_abs, bits, rm) ;
2739+ let x2 = x_abs. mul ( & x_abs, work_bits, rm) ;
26572740 let mut term = x_abs. clone ( ) ;
26582741 let mut sum = x_abs. clone ( ) ;
26592742
2660- let max_iterations = bits * 2 + 100 ;
2743+ let max_iterations = work_bits * 2 + 100 ;
26612744 for n in 1 ..max_iterations {
2662- term = term. mul ( & x2, bits , rm) ;
2663- let n_bf = BigFloat :: from_i32 ( n as i32 , bits ) ;
2664- term = term. div ( & n_bf, bits , rm) ;
2745+ term = term. mul ( & x2, work_bits , rm) ;
2746+ let n_bf = BigFloat :: from_i32 ( n as i32 , work_bits ) ;
2747+ term = term. div ( & n_bf, work_bits , rm) ;
26652748
2666- let denom = BigFloat :: from_i32 ( ( 2 * n + 1 ) as i32 , bits ) ;
2667- let contribution = term. div ( & denom, bits , rm) ;
2749+ let denom = BigFloat :: from_i32 ( ( 2 * n + 1 ) as i32 , work_bits ) ;
2750+ let contribution = term. div ( & denom, work_bits , rm) ;
26682751
26692752 if n % 2 == 1 {
2670- sum = sum. sub ( & contribution, bits , rm) ;
2753+ sum = sum. sub ( & contribution, work_bits , rm) ;
26712754 } else {
2672- sum = sum. add ( & contribution, bits , rm) ;
2755+ sum = sum. add ( & contribution, work_bits , rm) ;
26732756 }
26742757
26752758 if contribution. is_zero ( ) {
26762759 break ;
26772760 }
26782761 if let ( Some ( c_exp) , Some ( s_exp) ) =
26792762 ( contribution. exponent ( ) , sum. exponent ( ) )
2680- && s_exp - c_exp > ( bits as i32 )
2763+ && s_exp - c_exp > ( work_bits as i32 )
26812764 {
26822765 break ;
26832766 }
26842767 }
26852768
2686- // Multiply by 2/sqrt(π)
2687- let two = BigFloat :: from_i32 ( 2 , bits ) ;
2688- let pi = cc. pi ( bits , rm) ;
2689- let sqrt_pi = pi. sqrt ( bits , rm) ;
2690- let factor = two. div ( & sqrt_pi, bits , rm) ;
2769+ // Multiply by 2/sqrt(π), round to final precision
2770+ let two = BigFloat :: from_i32 ( 2 , work_bits ) ;
2771+ let pi = cc. pi ( work_bits , rm) ;
2772+ let sqrt_pi = pi. sqrt ( work_bits , rm) ;
2773+ let factor = two. div ( & sqrt_pi, work_bits , rm) ;
26912774 let result = sum. mul ( & factor, bits, rm) ;
26922775
26932776 if is_negative { result. neg ( ) } else { result }
26942777}
26952778
2779+ /// Compute erfc(x) with arbitrary precision.
2780+ /// For large x, uses continued fraction directly. For small x, uses 1 - erf(x).
2781+ fn bigfloat_erfc (
2782+ x : & astro_float:: BigFloat ,
2783+ bits : usize ,
2784+ rm : astro_float:: RoundingMode ,
2785+ cc : & mut astro_float:: Consts ,
2786+ ) -> astro_float:: BigFloat {
2787+ use astro_float:: BigFloat ;
2788+
2789+ if x. is_zero ( ) {
2790+ return BigFloat :: from_i32 ( 1 , bits) ;
2791+ }
2792+
2793+ let is_negative = x. is_negative ( ) ;
2794+ let x_abs = x. abs ( ) ;
2795+
2796+ let four = BigFloat :: from_i32 ( 4 , bits) ;
2797+ let result = if x_abs. cmp ( & four) == Some ( 1 ) {
2798+ // |x| > 4: use continued fraction directly for best precision
2799+ bigfloat_erfc_cf ( & x_abs, bits, rm, cc)
2800+ } else {
2801+ // |x| <= 4: compute via 1 - erf(x)
2802+ let erf_val = bigfloat_erf ( & x_abs, bits, rm, cc) ;
2803+ let one = BigFloat :: from_i32 ( 1 , bits) ;
2804+ one. sub ( & erf_val, bits, rm)
2805+ } ;
2806+
2807+ // erfc(-x) = 2 - erfc(x)
2808+ if is_negative {
2809+ let two = BigFloat :: from_i32 ( 2 , bits) ;
2810+ two. sub ( & result, bits, rm)
2811+ } else {
2812+ result
2813+ }
2814+ }
2815+
26962816/// Compute the exponential integral Ei(x) using BigFloat arithmetic.
26972817/// For real x: Ei(x) = γ + ln|x| + Σ_{n=1}^{∞} x^n / (n * n!)
26982818/// where γ is the Euler-Mascheroni constant.
0 commit comments