Skip to main content

zenith_float_num/
complex_special.rs

1//! Complex `erf` / `erfc` (Faddeeva) and `gamma` / `ln_gamma` / `digamma`.
2//!
3//! Software-limb [`ExactComplex`] arithmetic only. The error-function path is the
4//! Faddeeva function throughout — not the real series evaluated at \(\lvert z\rvert\).
5
6use crate::common::util::bump_prec_retry;
7use crate::common::util::round_p;
8use crate::Consts;
9use crate::Error;
10use crate::ExactComplex;
11use crate::ExactNum;
12use crate::RoundingMode;
13use crate::WORD_BIT_SIZE;
14use alloc::vec::Vec;
15
16/// L1 radius \(\lvert\mathrm{Re}\,z\rvert+\lvert\mathrm{Im}\,z\rvert\) below which
17/// the Faddeeva power series is used (Poppe–Wijers inner region).
18const FADDEEVA_SERIES_L1: u32 = 8;
19
20/// Bernoulli terms kept in the Stirling series for \(\ln\Gamma\) and \(\psi\).
21const GAMMA_STIRLING_TERMS: usize = 64;
22
23/// Raise \(z\) by integers until \(\mathrm{exponent}(\lvert z\rvert)\) is at least this
24/// (\(\lvert z\rvert\ge 2^{k}\)) so Stirling terms decay.
25const GAMMA_STIRLING_MIN_ABS_EXP: i32 = 6;
26
27/// Same shift target for digamma (real kernel uses exponent \(< 8\)).
28const DIGAMMA_STIRLING_MIN_ABS_EXP: i32 = 8;
29
30/// Positive integers \(n\) for which \(\Gamma(n)=(n-1)!\) is evaluated by multiplying
31/// \(1\ldots n-1\) instead of Stirling.
32const GAMMA_FACTORIAL_MAX: u32 = 64;
33
34pub(crate) fn nan_pair(e: Error) -> ExactComplex {
35    ExactComplex::new(ExactNum::nan(Some(e)), ExactNum::nan(Some(e)))
36}
37
38pub(crate) fn neg_c(z: &ExactComplex) -> ExactComplex {
39    ExactComplex::new(z.re().neg(), z.im().neg())
40}
41
42pub(crate) fn two_c(p: usize) -> ExactComplex {
43    ExactComplex::from_real(ExactNum::from_u8(2, p), p)
44}
45
46pub(crate) fn half_c(p: usize) -> ExactComplex {
47    let h = ExactNum::from_u8(1, p).div(&ExactNum::from_u8(2, p), p, RoundingMode::None);
48    ExactComplex::from_real(h, p)
49}
50
51pub(crate) fn pi_c(p: usize, cc: &mut Consts) -> ExactComplex {
52    ExactComplex::from_real(cc.pi(p, RoundingMode::None), p)
53}
54
55pub(crate) fn term_negligible(t: &ExactComplex, p: usize) -> bool {
56    let m = t.abs(p, RoundingMode::None);
57    m.is_zero()
58        || m.exponent()
59            .is_some_and(|e| (e as isize) + (p as isize) < 0)
60}
61
62fn l1_below_series_bound(z: &ExactComplex, p: usize) -> bool {
63    let s = z.re().abs().add(&z.im().abs(), p, RoundingMode::None);
64    let bound = ExactNum::from_u32(FADDEEVA_SERIES_L1, p);
65    matches!(s.cmp(&bound), Some(c) if c < 0)
66}
67
68/// Power series when inside the Poppe–Wijers L1 ball, or when \(\lvert z\rvert^2 < p\)
69/// so a truncated asymptotic cannot meet working precision (optimal cut is \(m\sim\lvert z\rvert^2\)).
70fn use_faddeeva_series(z: &ExactComplex, p: usize) -> bool {
71    if l1_below_series_bound(z, p) {
72        return true;
73    }
74    let az = z.abs(p, RoundingMode::None);
75    let az2 = az.mul(&az, p, RoundingMode::None);
76    let thresh = ExactNum::from_u32(p.min(u32::MAX as usize) as u32, p);
77    matches!(az2.cmp(&thresh), Some(c) if c < 0)
78}
79
80fn re_positive(z: &ExactComplex) -> bool {
81    z.re().is_positive() && !z.re().is_zero()
82}
83
84fn im_negative_or_neg_real(z: &ExactComplex) -> bool {
85    z.im().is_negative() || (z.im().is_zero() && z.re().is_negative())
86}
87
88pub(crate) fn is_nonpos_integer(z: &ExactComplex) -> bool {
89    z.im().is_zero() && z.re().is_int() && (z.re().is_zero() || z.re().is_negative())
90}
91
92fn abs_needs_shift(z: &ExactComplex, p: usize, min_exp: i32) -> bool {
93    match z.abs(p, RoundingMode::None).exponent() {
94        Some(e) => e < min_exp,
95        None => false,
96    }
97}
98
99pub(crate) fn series_term_cap(p: usize) -> usize {
100    p.saturating_add(WORD_BIT_SIZE)
101}
102
103pub(crate) fn ziv_complex<F>(p: usize, rm: RoundingMode, mut compute: F) -> ExactComplex
104where
105    F: FnMut(usize) -> ExactComplex,
106{
107    let p = round_p(p);
108    let mut p_inc = WORD_BIT_SIZE;
109    let Some(mut p_wrk) = p.checked_add(p_inc) else {
110        return nan_pair(Error::InvalidArgument);
111    };
112    p_wrk = round_p(p_wrk);
113    loop {
114        let p_x = match p_wrk.checked_add(WORD_BIT_SIZE.saturating_mul(2)) {
115            Some(v) => v,
116            None => return nan_pair(Error::InvalidArgument),
117        };
118        let z = compute(p_x);
119        let mut re = z.re().clone();
120        let mut im = z.im().clone();
121        let ok_re = re.try_set_precision(p, rm, p_wrk);
122        let ok_im = im.try_set_precision(p, rm, p_wrk);
123        if ok_re && ok_im {
124            return ExactComplex::new(re, im);
125        }
126        if bump_prec_retry(&mut p_wrk, &mut p_inc, p).is_err() {
127            return nan_pair(Error::PrecisionRetryExhausted);
128        }
129    }
130}
131
132/// Even Bernoulli numbers \(B_2,\ldots,B_{2k_{\max}}\) via one Akiyama–Tanigawa pass.
133fn even_bernoulli_numbers(kmax: usize, p: usize) -> Vec<ExactNum> {
134    let m = 2 * kmax;
135    let mut a: Vec<ExactNum> = Vec::new();
136    for i in 0..=m {
137        let num = ExactNum::from_u8(1, p);
138        let den = ExactNum::from_u32((i + 1) as u32, p);
139        a.push(num.div(&den, p, RoundingMode::None));
140    }
141    let mut evens = Vec::new();
142    for j in 1..=m {
143        for i in 0..=(m - j) {
144            let diff = a[i].sub(&a[i + 1], p, RoundingMode::None);
145            let fac = ExactNum::from_u32((i + 1) as u32, p);
146            a[i] = fac.mul(&diff, p, RoundingMode::None);
147        }
148        if j % 2 == 0 {
149            evens.push(a[0].clone());
150        }
151    }
152    evens
153}
154
155fn factorial_um1(n: u32, p: usize) -> ExactComplex {
156    let mut acc = ExactNum::from_u8(1, p);
157    if n >= 2 {
158        for k in 2..n {
159            acc = acc.mul(&ExactNum::from_u32(k, p), p, RoundingMode::None);
160        }
161    }
162    ExactComplex::from_real(acc, p)
163}
164
165impl ExactComplex {
166    /// Error function \(\mathrm{erf}(z)=1-\mathrm{erfc}(z)\). Entire; NaN in → NaN out.
167    ///
168    /// # Precision
169    ///
170    /// - Algorithm: Faddeeva `w(z)` series for `|z|` below `FADDEEVA_SERIES_L1 = 8`; continued fraction otherwise.
171    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
172    /// - MPFR oracle: real axis vs `mpfr_erf`. GNU MPC has no `mpc_erf`. Off-axis: `erf` odd, `erfc=1-erf`.
173    pub fn erf(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
174        if self.is_nan() {
175            return ExactComplex::new(self.re().clone(), self.im().clone());
176        }
177        let dest = round_p(p);
178        ziv_complex(dest, rm, |pw| self.erf_at(pw, dest, cc))
179    }
180
181    /// Complementary error function via Faddeeva: \(\mathrm{erfc}(z)=e^{-z^2}w(iz)\).
182    /// Entire; NaN in → NaN out.
183    ///
184    /// # Precision
185    ///
186    /// - Algorithm: same Faddeeva path as [`Self::erf`].
187    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
188    /// - MPFR oracle: real axis vs `mpfr_erfc` (via `1-erf` identity). GNU MPC has no `mpc_erfc`.
189    pub fn erfc(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
190        if self.is_nan() {
191            return ExactComplex::new(self.re().clone(), self.im().clone());
192        }
193        let dest = round_p(p);
194        ziv_complex(dest, rm, |pw| self.erfc_at(pw, dest, cc))
195    }
196
197    /// Gamma \(\Gamma(z)\). Poles at non-positive integers → NaN.
198    ///
199    /// # Precision
200    ///
201    /// - Algorithm: Stirling (`GAMMA_STIRLING_TERMS = 64`) plus reflection; factorial for small integers (`GAMMA_FACTORIAL_MAX = 64`).
202    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
203    /// - MPFR oracle: real axis vs `mpfr_gamma`. GNU MPC has no `mpc_gamma`. Integers: `Γ(n)=(n-1)!`.
204    pub fn gamma(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
205        if self.is_nan() {
206            return ExactComplex::new(self.re().clone(), self.im().clone());
207        }
208        if is_nonpos_integer(self) {
209            return nan_pair(Error::InvalidArgument);
210        }
211        ziv_complex(round_p(p), rm, |pw| self.gamma_at(pw, cc))
212    }
213
214    /// Principal \(\ln\Gamma(z)\). Cut on \((-\infty,0]\); poles → NaN.
215    /// Equals \(\ln(\Gamma(z))\) with the principal logarithm.
216    ///
217    /// # Precision
218    ///
219    /// - Algorithm: Stirling (`GAMMA_STIRLING_TERMS = 64`) plus reflection.
220    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
221    /// - MPFR oracle: no.
222    pub fn ln_gamma(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
223        if self.is_nan() {
224            return ExactComplex::new(self.re().clone(), self.im().clone());
225        }
226        if is_nonpos_integer(self) {
227            return nan_pair(Error::InvalidArgument);
228        }
229        ziv_complex(round_p(p), rm, |pw| self.ln_gamma_at(pw, cc))
230    }
231
232    /// Digamma \(\psi(z)=\Gamma'/\Gamma\). Poles at non-positive integers → NaN.
233    ///
234    /// # Precision
235    ///
236    /// - Algorithm: recurrence plus Bernoulli; reflection for \(\operatorname{Re} z < 0\).
237    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
238    /// - MPFR oracle: no.
239    pub fn digamma(&self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
240        if self.is_nan() {
241            return ExactComplex::new(self.re().clone(), self.im().clone());
242        }
243        if is_nonpos_integer(self) {
244            return nan_pair(Error::InvalidArgument);
245        }
246        ziv_complex(round_p(p), rm, |pw| self.digamma_at(pw, cc))
247    }
248
249    fn erf_at(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
250        if use_faddeeva_series(self, dest_p) {
251            Self::erf_power_series(self, work_p, cc)
252        } else {
253            ExactComplex::one(work_p).sub(
254                &self.erfc_via_faddeeva(work_p, dest_p, cc),
255                work_p,
256                RoundingMode::None,
257            )
258        }
259    }
260
261    fn erfc_at(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
262        if use_faddeeva_series(self, dest_p) {
263            ExactComplex::one(work_p).sub(
264                &Self::erf_power_series(self, work_p, cc),
265                work_p,
266                RoundingMode::None,
267            )
268        } else {
269            self.erfc_via_faddeeva(work_p, dest_p, cc)
270        }
271    }
272
273    /// `erfc(z) = e^{-z²} w(iz)`, with the reflection form when `Re(z) ≤ 0`.
274    fn erfc_via_faddeeva(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
275        let z2 = self.mul(self, work_p, RoundingMode::None);
276        let em = neg_c(&z2).exp(work_p, RoundingMode::None, cc);
277        let iz = ExactComplex::i(work_p).mul(self, work_p, RoundingMode::None);
278        if re_positive(self) {
279            em.mul(
280                &Self::faddeeva(&iz, work_p, dest_p, cc),
281                work_p,
282                RoundingMode::None,
283            )
284        } else {
285            two_c(work_p).sub(
286                &em.mul(
287                    &Self::faddeeva(&neg_c(&iz), work_p, dest_p, cc),
288                    work_p,
289                    RoundingMode::None,
290                ),
291                work_p,
292                RoundingMode::None,
293            )
294        }
295    }
296
297    /// Faddeeva \(w(z)=e^{-z^2}\mathrm{erfc}(-iz)\). Reduces to \(\mathrm{Im}\,z\ge 0\).
298    fn faddeeva(z: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
299        if im_negative_or_neg_real(z) {
300            let z2 = z.mul(z, work_p, RoundingMode::None);
301            let two_exp = two_c(work_p).mul(
302                &neg_c(&z2).exp(work_p, RoundingMode::None, cc),
303                work_p,
304                RoundingMode::None,
305            );
306            return two_exp.sub(
307                &Self::faddeeva_upper(&neg_c(z), work_p, dest_p, cc),
308                work_p,
309                RoundingMode::None,
310            );
311        }
312        Self::faddeeva_upper(z, work_p, dest_p, cc)
313    }
314
315    fn faddeeva_upper(z: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
316        if use_faddeeva_series(z, dest_p) {
317            Self::faddeeva_series(z, work_p, cc)
318        } else {
319            Self::faddeeva_asymp(z, work_p, cc)
320        }
321    }
322
323    /// Poppe–Wijers power series: \(w(z)=e^{-z^2}(1-\mathrm{erf}(-iz))\).
324    fn faddeeva_series(z: &Self, p: usize, cc: &mut Consts) -> Self {
325        let u = neg_c(&ExactComplex::i(p)).mul(z, p, RoundingMode::None);
326        let erf_u = Self::erf_power_series(&u, p, cc);
327        let erfc_u = ExactComplex::one(p).sub(&erf_u, p, RoundingMode::None);
328        let z2 = z.mul(z, p, RoundingMode::None);
329        neg_c(&z2)
330            .exp(p, RoundingMode::None, cc)
331            .mul(&erfc_u, p, RoundingMode::None)
332    }
333
334    /// Entire power series \(\mathrm{erf}(u)=\frac{2}{\sqrt\pi}\sum(-1)^n u^{2n+1}/(n!(2n+1))\).
335    fn erf_power_series(u: &Self, p: usize, cc: &mut Consts) -> Self {
336        let pi = cc.pi(p, RoundingMode::None);
337        let sqrt_pi = pi.sqrt(p, RoundingMode::None);
338        let scale = ExactNum::from_u8(2, p).div(&sqrt_pi, p, RoundingMode::None);
339        let u2 = u.mul(u, p, RoundingMode::None);
340        let mut upow = u.clone();
341        let mut nfact = ExactNum::from_u8(1, p);
342        let mut sum = u.clone();
343        for n in 1..=series_term_cap(p) {
344            nfact = nfact.mul(&ExactNum::from_u32(n as u32, p), p, RoundingMode::None);
345            upow = upow.mul(&u2, p, RoundingMode::None);
346            let two_n_1 = ExactNum::from_u32((2 * n + 1) as u32, p);
347            let den = nfact.mul(&two_n_1, p, RoundingMode::None);
348            let mut t = upow.div(&ExactComplex::from_real(den, p), p, RoundingMode::None);
349            if n % 2 == 1 {
350                t = neg_c(&t);
351            }
352            sum = sum.add(&t, p, RoundingMode::None);
353            if term_negligible(&t, p) {
354                break;
355            }
356        }
357        ExactComplex::from_real(scale, p).mul(&sum, p, RoundingMode::None)
358    }
359
360    /// \(w(z)\sim i/(z\sqrt\pi)\sum(2m-1)!!/(2z^2)^m\) for \(\mathrm{Im}\,z\ge 0\).
361    fn faddeeva_asymp(z: &Self, p: usize, cc: &mut Consts) -> Self {
362        let z2 = z.mul(z, p, RoundingMode::None);
363        let two_z2 = two_c(p).mul(&z2, p, RoundingMode::None);
364        let mut term = ExactComplex::one(p);
365        let mut s = ExactComplex::one(p);
366        let mut prev_e = i32::MIN;
367        for m in 1..=series_term_cap(p) {
368            let odd = ExactComplex::from_real(ExactNum::from_u32((2 * m - 1) as u32, p), p);
369            term = term
370                .mul(&odd, p, RoundingMode::None)
371                .div(&two_z2, p, RoundingMode::None);
372            s = s.add(&term, p, RoundingMode::None);
373            let e = term
374                .abs(p, RoundingMode::None)
375                .exponent()
376                .unwrap_or(i32::MIN);
377            if term_negligible(&term, p) {
378                break;
379            }
380            if m > 1 && e > prev_e {
381                break;
382            }
383            prev_e = e;
384        }
385        let sqrt_pi =
386            ExactComplex::from_real(cc.pi(p, RoundingMode::None).sqrt(p, RoundingMode::None), p);
387        let den = z.mul(&sqrt_pi, p, RoundingMode::None);
388        ExactComplex::i(p)
389            .div(&den, p, RoundingMode::None)
390            .mul(&s, p, RoundingMode::None)
391    }
392
393    pub(crate) fn gamma_at(&self, p: usize, cc: &mut Consts) -> Self {
394        if let Some(n) = self.small_pos_int(p) {
395            return factorial_um1(n, p);
396        }
397        if !re_positive(self) && !self.re().is_zero() {
398            let pi = pi_c(p, cc);
399            let piz = pi.mul(self, p, RoundingMode::None);
400            let s = piz.sin(p, RoundingMode::None, cc);
401            let omz = ExactComplex::one(p).sub(self, p, RoundingMode::None);
402            let g = omz.gamma_positive(p, cc);
403            return pi.div(&s.mul(&g, p, RoundingMode::None), p, RoundingMode::None);
404        }
405        self.gamma_positive(p, cc)
406    }
407
408    fn small_pos_int(&self, p: usize) -> Option<u32> {
409        if !self.im().is_zero() || !self.re().is_int() || !self.re().is_positive() {
410            return None;
411        }
412        for n in 1u32..=GAMMA_FACTORIAL_MAX {
413            let w = ExactNum::from_u32(n, p);
414            if self.re().cmp(&w) == Some(0) {
415                return Some(n);
416            }
417        }
418        None
419    }
420
421    fn gamma_positive(&self, p: usize, cc: &mut Consts) -> Self {
422        let one = ExactComplex::one(p);
423        let mut z = self.clone();
424        let mut acc = ExactComplex::one(p);
425        while abs_needs_shift(&z, p, GAMMA_STIRLING_MIN_ABS_EXP) {
426            acc = acc.mul(&z, p, RoundingMode::None);
427            z = z.add(&one, p, RoundingMode::None);
428        }
429        let lg = z.ln_gamma_stirling(p, cc);
430        let g = lg.exp(p, RoundingMode::None, cc);
431        g.div(&acc, p, RoundingMode::None)
432    }
433
434    fn ln_gamma_at(&self, p: usize, cc: &mut Consts) -> Self {
435        if let Some(n) = self.small_pos_int(p) {
436            return factorial_um1(n, p).ln(p, RoundingMode::None, cc);
437        }
438        if !re_positive(self) && !self.re().is_zero() {
439            let piz = pi_c(p, cc).mul(self, p, RoundingMode::None);
440            let ln_sin = piz
441                .sin(p, RoundingMode::None, cc)
442                .ln(p, RoundingMode::None, cc);
443            let omz = ExactComplex::one(p).sub(self, p, RoundingMode::None);
444            return pi_c(p, cc)
445                .ln(p, RoundingMode::None, cc)
446                .sub(&ln_sin, p, RoundingMode::None)
447                .sub(&omz.ln_gamma_positive(p, cc), p, RoundingMode::None);
448        }
449        self.ln_gamma_positive(p, cc)
450    }
451
452    fn ln_gamma_positive(&self, p: usize, cc: &mut Consts) -> Self {
453        let one = ExactComplex::one(p);
454        let mut z = self.clone();
455        let mut ln_acc = ExactComplex::zero(p);
456        while abs_needs_shift(&z, p, GAMMA_STIRLING_MIN_ABS_EXP) {
457            ln_acc = ln_acc.add(&z.ln(p, RoundingMode::None, cc), p, RoundingMode::None);
458            z = z.add(&one, p, RoundingMode::None);
459        }
460        z.ln_gamma_stirling(p, cc)
461            .sub(&ln_acc, p, RoundingMode::None)
462    }
463
464    fn ln_gamma_stirling(&self, p: usize, cc: &mut Consts) -> Self {
465        let ln_z = self.ln(p, RoundingMode::None, cc);
466        let zmh = self.sub(&half_c(p), p, RoundingMode::None);
467        let mut s = zmh.mul(&ln_z, p, RoundingMode::None);
468        s = s.sub(self, p, RoundingMode::None);
469        let two_pi = two_c(p).mul(&pi_c(p, cc), p, RoundingMode::None);
470        let ln_two_pi = two_pi.ln(p, RoundingMode::None, cc);
471        s = s.add(
472            &ln_two_pi.mul(&half_c(p), p, RoundingMode::None),
473            p,
474            RoundingMode::None,
475        );
476        let bs = even_bernoulli_numbers(GAMMA_STIRLING_TERMS, p);
477        let mut zpow = self.clone();
478        let mut prev_e = i32::MIN;
479        for (k, b) in bs.iter().enumerate() {
480            let k = k + 1;
481            let two_k = ExactNum::from_u32((2 * k) as u32, p);
482            let two_k_m1 = ExactNum::from_u32((2 * k - 1) as u32, p);
483            let den_r = two_k.mul(&two_k_m1, p, RoundingMode::None);
484            let den = ExactComplex::from_real(den_r, p).mul(&zpow, p, RoundingMode::None);
485            let term = ExactComplex::from_real(b.clone(), p).div(&den, p, RoundingMode::None);
486            s = s.add(&term, p, RoundingMode::None);
487            let e = term
488                .abs(p, RoundingMode::None)
489                .exponent()
490                .unwrap_or(i32::MIN);
491            if term_negligible(&term, p) {
492                break;
493            }
494            if k > 2 && e > prev_e {
495                break;
496            }
497            prev_e = e;
498            zpow = zpow
499                .mul(self, p, RoundingMode::None)
500                .mul(self, p, RoundingMode::None);
501        }
502        s
503    }
504
505    fn digamma_at(&self, p: usize, cc: &mut Consts) -> Self {
506        if !re_positive(self) && !self.re().is_zero() {
507            let one = ExactComplex::one(p);
508            let omz = one.sub(self, p, RoundingMode::None);
509            let psi = omz.digamma_positive(p, cc);
510            let piz = pi_c(p, cc).mul(self, p, RoundingMode::None);
511            let cot = piz.cos(p, RoundingMode::None, cc).div(
512                &piz.sin(p, RoundingMode::None, cc),
513                p,
514                RoundingMode::None,
515            );
516            return psi.sub(
517                &pi_c(p, cc).mul(&cot, p, RoundingMode::None),
518                p,
519                RoundingMode::None,
520            );
521        }
522        self.digamma_positive(p, cc)
523    }
524
525    fn digamma_positive(&self, p: usize, cc: &mut Consts) -> Self {
526        let one = ExactComplex::one(p);
527        let mut z = self.clone();
528        let mut acc = ExactComplex::zero(p);
529        while abs_needs_shift(&z, p, DIGAMMA_STIRLING_MIN_ABS_EXP) {
530            let rec = one.div(&z, p, RoundingMode::None);
531            acc = acc.sub(&rec, p, RoundingMode::None);
532            z = z.add(&one, p, RoundingMode::None);
533        }
534        acc.add(&z.digamma_asymp(p, cc), p, RoundingMode::None)
535    }
536
537    fn digamma_asymp(&self, p: usize, cc: &mut Consts) -> Self {
538        let ln_z = self.ln(p, RoundingMode::None, cc);
539        let two_z = two_c(p).mul(self, p, RoundingMode::None);
540        let half_inv = ExactComplex::one(p).div(&two_z, p, RoundingMode::None);
541        let mut s = ln_z.sub(&half_inv, p, RoundingMode::None);
542        let z2 = self.mul(self, p, RoundingMode::None);
543        let mut zp = ExactComplex::one(p);
544        let bs = even_bernoulli_numbers(GAMMA_STIRLING_TERMS, p);
545        let mut prev_e = i32::MIN;
546        for (k, b) in bs.iter().enumerate() {
547            let k = k + 1;
548            zp = zp.mul(&z2, p, RoundingMode::None);
549            let two_k = ExactComplex::from_real(ExactNum::from_u32((2 * k) as u32, p), p);
550            let den = two_k.mul(&zp, p, RoundingMode::None);
551            let term = ExactComplex::from_real(b.clone(), p).div(&den, p, RoundingMode::None);
552            s = s.sub(&term, p, RoundingMode::None);
553            let e = term
554                .abs(p, RoundingMode::None)
555                .exponent()
556                .unwrap_or(i32::MIN);
557            if term_negligible(&term, p) {
558                break;
559            }
560            if k > 2 && e > prev_e {
561                break;
562            }
563            prev_e = e;
564        }
565        s
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    fn near_bits(a: &ExactNum, b: &ExactNum, p: usize, slack: i32) -> bool {
574        let d = a.sub(b, p, RoundingMode::None).abs();
575        d.is_zero() || d.exponent().is_some_and(|e| e < -((p as i32) / slack))
576    }
577
578    fn near(a: &ExactNum, b: &ExactNum, p: usize) -> bool {
579        near_bits(a, b, p, 4)
580    }
581
582    fn cnear(a: &ExactComplex, b: &ExactComplex, p: usize) -> bool {
583        near(a.re(), b.re(), p) && near(a.im(), b.im(), p)
584    }
585
586    fn cnear_bits(a: &ExactComplex, b: &ExactComplex, p: usize, slack: i32) -> bool {
587        near_bits(a.re(), b.re(), p, slack) && near_bits(a.im(), b.im(), p, slack)
588    }
589
590    fn tiny(x: &ExactNum, p: usize) -> bool {
591        x.is_zero() || x.exponent().is_some_and(|e| e < -((p as i32) / 4))
592    }
593
594    /// Independent real series \(\mathrm{erfi}(x)=\frac{2}{\sqrt\pi}\sum x^{2n+1}/(n!(2n+1))\).
595    fn erfi_real(x: &ExactNum, p: usize, cc: &mut Consts) -> ExactNum {
596        let pi = cc.pi(p, RoundingMode::None);
597        let sqrt_pi = pi.sqrt(p, RoundingMode::None);
598        let scale = ExactNum::from_u8(2, p).div(&sqrt_pi, p, RoundingMode::None);
599        let x2 = x.mul(x, p, RoundingMode::None);
600        let mut xpow = x.clone();
601        let mut nfact = ExactNum::from_u8(1, p);
602        let mut sum = xpow.clone();
603        for n in 1..=series_term_cap(p) {
604            nfact = nfact.mul(&ExactNum::from_u32(n as u32, p), p, RoundingMode::None);
605            xpow = xpow.mul(&x2, p, RoundingMode::None);
606            let den = nfact.mul(
607                &ExactNum::from_u32((2 * n + 1) as u32, p),
608                p,
609                RoundingMode::None,
610            );
611            let t = xpow.div(&den, p, RoundingMode::None);
612            sum = sum.add(&t, p, RoundingMode::None);
613            if t.is_zero()
614                || t.exponent()
615                    .is_some_and(|e| (e as isize) + (p as isize) < 0)
616            {
617                break;
618            }
619        }
620        scale.mul(&sum, p, RoundingMode::None)
621    }
622
623    #[test]
624    fn test_complex_erf_golds() {
625        let p = 256;
626        let rm = RoundingMode::ToEven;
627        let mut cc = Consts::new().unwrap();
628
629        let z0 = ExactComplex::zero(p);
630        let e0 = z0.erf(p, rm, &mut cc);
631        assert!(tiny(e0.re(), p) && tiny(e0.im(), p));
632
633        let one = ExactComplex::one(p);
634        let e1 = one.erf(p, rm, &mut cc);
635        let r1 = ExactNum::from_u8(1, p).erf(p, rm, &mut cc);
636        assert!(near(e1.re(), &r1, p));
637        assert!(tiny(e1.im(), p));
638
639        let z = ExactComplex::new(ExactNum::from_u8(1, p), ExactNum::from_u8(1, p));
640        let ez = z.erf(p, rm, &mut cc);
641        let em = neg_c(&z).erf(p, rm, &mut cc);
642        assert!(cnear(&ez, &neg_c(&em), p));
643
644        let one_c = ExactComplex::one(p);
645        let erfc_z = z.erfc(p, rm, &mut cc);
646        let id = one_c.sub(&ez, p, rm);
647        assert!(cnear(&erfc_z, &id, p));
648
649        let i = ExactComplex::i(p);
650        let ei = i.erf(p, rm, &mut cc);
651        let erfi1 = erfi_real(&ExactNum::from_u8(1, p), p, &mut cc);
652        assert!(tiny(ei.re(), p));
653        assert!(near(ei.im(), &erfi1, p));
654
655        let h = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
656        let hc = ExactComplex::from_real(h.clone(), p);
657        let zp = z.add(&hc, p, rm);
658        let zm = z.sub(&hc, p, rm);
659        let num = zp.erf(p, rm, &mut cc).sub(&zm.erf(p, rm, &mut cc), p, rm);
660        let two_h = hc.mul(&two_c(p), p, rm);
661        let deriv = num.div(&two_h, p, rm);
662        let z2 = z.mul(&z, p, rm);
663        let expm = neg_c(&z2).exp(p, rm, &mut cc);
664        let two_over = ExactNum::from_u8(2, p).div(&cc.pi(p, rm).sqrt(p, rm), p, rm);
665        let expect = ExactComplex::from_real(two_over, p).mul(&expm, p, rm);
666        assert!(cnear_bits(&deriv, &expect, p, 8));
667
668        let nan = ExactComplex::new(crate::NAN.clone(), ExactNum::new(p));
669        assert!(nan.erf(p, rm, &mut cc).is_nan());
670        assert!(nan.erfc(p, rm, &mut cc).is_nan());
671
672        let big = ExactComplex::from_real(ExactNum::from_u8(10, p), p);
673        let eb = big.erf(p, rm, &mut cc);
674        assert!(near(eb.re(), &ExactNum::from_u8(1, p), p));
675        assert!(tiny(eb.im(), p));
676        assert!(cnear(&eb, &neg_c(&neg_c(&big).erf(p, rm, &mut cc)), p));
677        let far = ExactComplex::from_real(ExactNum::from_u8(20, p), p);
678        let ef = far.erf(p, rm, &mut cc);
679        assert!(near(ef.re(), &ExactNum::from_u8(1, p), p));
680        assert!(tiny(ef.im(), p));
681    }
682
683    #[test]
684    fn test_complex_gamma_golds() {
685        let p = 256;
686        let rm = RoundingMode::ToEven;
687        let mut cc = Consts::new().unwrap();
688
689        let one = ExactComplex::one(p);
690        let g1 = one.gamma(p, rm, &mut cc);
691        assert!(cnear(&g1, &one, p));
692
693        let two = two_c(p);
694        let g2 = two.gamma(p, rm, &mut cc);
695        assert!(cnear(&g2, &one, p));
696
697        let half = half_c(p);
698        let ghalf = half.gamma(p, rm, &mut cc);
699        let sqrt_pi = cc.pi(p, rm).sqrt(p, rm);
700        assert!(near(ghalf.re(), &sqrt_pi, p));
701        assert!(tiny(ghalf.im(), p));
702
703        let five = ExactComplex::from_real(ExactNum::from_u8(5, p), p);
704        let g5 = five.gamma(p, rm, &mut cc);
705        let tf = ExactComplex::from_real(ExactNum::from_u8(24, p), p);
706        assert!(cnear(&g5, &tf, p));
707
708        let z = ExactComplex::new(
709            ExactNum::from_u8(1, p).div(&ExactNum::from_u8(3, p), p, rm),
710            ExactNum::from_u8(2, p).div(&ExactNum::from_u8(5, p), p, rm),
711        );
712        let gz = z.gamma(p, rm, &mut cc);
713        let omz = one.sub(&z, p, rm);
714        let gom = omz.gamma(p, rm, &mut cc);
715        let lhs = gz.mul(&gom, p, rm);
716        let piz = pi_c(p, &mut cc).mul(&z, p, rm);
717        let rhs = pi_c(p, &mut cc).div(&piz.sin(p, rm, &mut cc), p, rm);
718        assert!(cnear(&lhs, &rhs, p));
719
720        let lg1 = one.ln_gamma(p, rm, &mut cc);
721        assert!(tiny(lg1.re(), p) && tiny(lg1.im(), p));
722
723        let lgh = half.ln_gamma(p, rm, &mut cc);
724        let ln_sqrt_pi = sqrt_pi.ln(p, rm, &mut cc);
725        assert!(near(lgh.re(), &ln_sqrt_pi, p));
726        assert!(tiny(lgh.im(), p));
727
728        let psi1 = one.digamma(p, rm, &mut cc);
729        let neg_g = cc.euler_gamma(p, rm).neg();
730        assert!(near(psi1.re(), &neg_g, p));
731        assert!(tiny(psi1.im(), p));
732
733        let zp1 = z.add(&one, p, rm);
734        let dpsi = zp1
735            .digamma(p, rm, &mut cc)
736            .sub(&z.digamma(p, rm, &mut cc), p, rm);
737        let rec = one.div(&z, p, rm);
738        assert!(cnear(&dpsi, &rec, p));
739
740        for n in [0i8, -1, -2] {
741            let pole = ExactComplex::from_real(ExactNum::from_i8(n, p), p);
742            assert!(pole.gamma(p, rm, &mut cc).is_nan(), "gamma pole {n}");
743            assert!(pole.ln_gamma(p, rm, &mut cc).is_nan(), "ln_gamma pole {n}");
744            assert!(pole.digamma(p, rm, &mut cc).is_nan(), "digamma pole {n}");
745        }
746
747        let h = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
748        let hc = ExactComplex::from_real(h, p);
749        let num = z.add(&hc, p, rm).ln_gamma(p, rm, &mut cc).sub(
750            &z.sub(&hc, p, rm).ln_gamma(p, rm, &mut cc),
751            p,
752            rm,
753        );
754        let deriv = num.div(&hc.mul(&two_c(p), p, rm), p, rm);
755        let psi = z.digamma(p, rm, &mut cc);
756        assert!(cnear_bits(&deriv, &psi, p, 8));
757
758        let nan = ExactComplex::new(crate::NAN.clone(), ExactNum::new(p));
759        assert!(nan.gamma(p, rm, &mut cc).is_nan());
760    }
761}