Skip to main content

zenith_float_num/
complex_bessel.rs

1//! Complex Bessel \(J_\nu,Y_\nu,I_\nu,K_\nu\).
2
3use crate::common::util::round_p;
4use crate::complex_special::half_c;
5use crate::complex_special::nan_pair;
6use crate::complex_special::neg_c;
7use crate::complex_special::pi_c;
8use crate::complex_special::series_term_cap;
9use crate::complex_special::term_negligible;
10use crate::complex_special::two_c;
11use crate::complex_special::ziv_complex;
12use crate::Consts;
13use crate::Error;
14use crate::ExactComplex;
15use crate::ExactNum;
16use crate::RoundingMode;
17
18/// Use the power series when \(\lvert z\rvert\) is below this (and \(\lvert z\rvert^2\)
19/// is large enough for the Hankel expansion only beyond destination precision).
20const BESSEL_SERIES_THRESHOLD: u32 = 16;
21
22/// Integers \(\lvert n\rvert\) recognized exactly for \(Y_n\) recurrence and \(z=0\).
23const BESSEL_INTEGER_MAX: i32 = 64;
24
25fn abs_below(z: &ExactComplex, bound: u32, p: usize) -> bool {
26    let a = z.abs(p, RoundingMode::None);
27    let b = ExactNum::from_u32(bound, p);
28    matches!(a.cmp(&b), Some(c) if c < 0)
29}
30
31fn use_bessel_series(z: &ExactComplex, dest_p: usize) -> bool {
32    if abs_below(z, BESSEL_SERIES_THRESHOLD, dest_p) {
33        return true;
34    }
35    let az = z.abs(dest_p, RoundingMode::None);
36    let az2 = az.mul(&az, dest_p, RoundingMode::None);
37    let thresh = ExactNum::from_u32(dest_p.min(u32::MAX as usize) as u32, dest_p);
38    matches!(az2.cmp(&thresh), Some(c) if c < 0)
39}
40
41fn integer_nu(nu: &ExactComplex, p: usize) -> Option<i32> {
42    if !nu.im().is_zero() || !nu.re().is_int() {
43        return None;
44    }
45    for n in -BESSEL_INTEGER_MAX..=BESSEL_INTEGER_MAX {
46        let w = ExactNum::from_i32(n, p);
47        if nu.re().cmp(&w) == Some(0) {
48            return Some(n);
49        }
50    }
51    None
52}
53
54fn harmonic(k: usize, p: usize) -> ExactNum {
55    let mut h = ExactNum::new(p);
56    for i in 1..=k {
57        let t =
58            ExactNum::from_u8(1, p).div(&ExactNum::from_u32(i as u32, p), p, RoundingMode::None);
59        h = h.add(&t, p, RoundingMode::None);
60    }
61    h
62}
63
64fn four_c(p: usize) -> ExactComplex {
65    ExactComplex::from_real(ExactNum::from_u8(4, p), p)
66}
67
68fn eight_c(p: usize) -> ExactComplex {
69    ExactComplex::from_real(ExactNum::from_u8(8, p), p)
70}
71
72impl ExactComplex {
73    /// \(J_\nu(z)\). Entire for integer \(\nu\); cut on \((-\infty,0]\) otherwise.
74    /// \(z=0\) with non-integer \(\nu\) → NaN.
75    ///
76    /// # Precision
77    ///
78    /// - Algorithm: series for `|z| < BESSEL_SERIES_THRESHOLD` (`16`); Hankel otherwise. Integer `|n| ≤ BESSEL_INTEGER_MAX` (`64`).
79    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
80    /// - MPFR oracle: no.
81    pub fn bessel_j_nu(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
82        if self.is_nan() || nu.is_nan() {
83            return nan_pair(Error::InvalidArgument);
84        }
85        let dest = round_p(p);
86        ziv_complex(dest, rm, |pw| self.bessel_j_at(nu, pw, dest, cc))
87    }
88
89    /// \(Y_\nu(z)\). Cut on \((-\infty,0]\); \(z=0\) → NaN.
90    ///
91    /// # Precision
92    ///
93    /// - Algorithm: from \(J_ν\); `BESSEL_SERIES_THRESHOLD = 16`.
94    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
95    /// - MPFR oracle: no.
96    pub fn bessel_y(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
97        if self.is_nan() || nu.is_nan() {
98            return nan_pair(Error::InvalidArgument);
99        }
100        if self.re().is_zero() && self.im().is_zero() {
101            return nan_pair(Error::InvalidArgument);
102        }
103        let dest = round_p(p);
104        ziv_complex(dest, rm, |pw| self.bessel_y_at(nu, pw, dest, cc))
105    }
106
107    /// \(I_\nu(z)=i^{-\nu}J_\nu(iz)\). Same cut rules as \(J_\nu\).
108    ///
109    /// # Precision
110    ///
111    /// - Algorithm: via [`Self::bessel_j_nu`]; `BESSEL_SERIES_THRESHOLD = 16`.
112    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
113    /// - MPFR oracle: no.
114    pub fn bessel_i(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
115        if self.is_nan() || nu.is_nan() {
116            return nan_pair(Error::InvalidArgument);
117        }
118        let dest = round_p(p);
119        ziv_complex(dest, rm, |pw| self.bessel_i_at(nu, pw, dest, cc))
120    }
121
122    /// \(K_\nu(z)=(\pi/2)\,i^{\nu+1}H_\nu^{(1)}(iz)\). Cut on \((-\infty,0]\); \(z=0\) → NaN.
123    ///
124    /// # Precision
125    ///
126    /// - Algorithm: Hankel of \(iz\); `BESSEL_SERIES_THRESHOLD = 16`.
127    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
128    /// - MPFR oracle: no.
129    pub fn bessel_k(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
130        if self.is_nan() || nu.is_nan() {
131            return nan_pair(Error::InvalidArgument);
132        }
133        if self.re().is_zero() && self.im().is_zero() {
134            return nan_pair(Error::InvalidArgument);
135        }
136        let dest = round_p(p);
137        ziv_complex(dest, rm, |pw| self.bessel_k_at(nu, pw, dest, cc))
138    }
139
140    fn bessel_j_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
141        if self.re().is_zero() && self.im().is_zero() {
142            return match integer_nu(nu, work_p) {
143                Some(0) => ExactComplex::one(work_p),
144                Some(_) => ExactComplex::zero(work_p),
145                None => nan_pair(Error::InvalidArgument),
146            };
147        }
148        if use_bessel_series(self, dest_p) {
149            self.bessel_j_series(nu, work_p, cc)
150        } else {
151            self.bessel_hankel_j(nu, work_p, cc)
152        }
153    }
154
155    fn bessel_y_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
156        if let Some(n) = integer_nu(nu, work_p) {
157            return self.bessel_y_int(n, work_p, dest_p, cc);
158        }
159        if use_bessel_series(self, dest_p) {
160            self.bessel_y_nonint(nu, work_p, dest_p, cc)
161        } else {
162            self.bessel_hankel_y(nu, work_p, cc)
163        }
164    }
165
166    fn bessel_i_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
167        if self.re().is_zero() && self.im().is_zero() {
168            return match integer_nu(nu, work_p) {
169                Some(0) => ExactComplex::one(work_p),
170                Some(n) if n > 0 => ExactComplex::zero(work_p),
171                _ => nan_pair(Error::InvalidArgument),
172            };
173        }
174        let iz = ExactComplex::i(work_p).mul(self, work_p, RoundingMode::None);
175        let j = iz.bessel_j_at(nu, work_p, dest_p, cc);
176        let ln_i = ExactComplex::i(work_p).ln(work_p, RoundingMode::None, cc);
177        let scale =
178            neg_c(nu)
179                .mul(&ln_i, work_p, RoundingMode::None)
180                .exp(work_p, RoundingMode::None, cc);
181        scale.mul(&j, work_p, RoundingMode::None)
182    }
183
184    fn bessel_k_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
185        let iz = ExactComplex::i(work_p).mul(self, work_p, RoundingMode::None);
186        let j = iz.bessel_j_at(nu, work_p, dest_p, cc);
187        let y = iz.bessel_y_at(nu, work_p, dest_p, cc);
188        let h1 = j.add(
189            &ExactComplex::i(work_p).mul(&y, work_p, RoundingMode::None),
190            work_p,
191            RoundingMode::None,
192        );
193        let ln_i = ExactComplex::i(work_p).ln(work_p, RoundingMode::None, cc);
194        let nu_p1 = nu.add(&ExactComplex::one(work_p), work_p, RoundingMode::None);
195        let i_pow =
196            nu_p1
197                .mul(&ln_i, work_p, RoundingMode::None)
198                .exp(work_p, RoundingMode::None, cc);
199        let half_pi = pi_c(work_p, cc).mul(&half_c(work_p), work_p, RoundingMode::None);
200        half_pi
201            .mul(&i_pow, work_p, RoundingMode::None)
202            .mul(&h1, work_p, RoundingMode::None)
203    }
204
205    fn bessel_j_series(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
206        let half = self.mul(&half_c(p), p, RoundingMode::None);
207        let pow = half.pow(nu, p, RoundingMode::None, cc);
208        let g =
209            nu.add(&ExactComplex::one(p), p, RoundingMode::None)
210                .gamma(p, RoundingMode::None, cc);
211        let mut term = pow.div(&g, p, RoundingMode::None);
212        let mut sum = term.clone();
213        let hh = half.mul(&half, p, RoundingMode::None);
214        for k in 1..=series_term_cap(p) {
215            let kk = ExactComplex::from_real(ExactNum::from_u32(k as u32, p), p);
216            let den = kk
217                .add(nu, p, RoundingMode::None)
218                .mul(&kk, p, RoundingMode::None);
219            term = term
220                .mul(&hh, p, RoundingMode::None)
221                .div(&den, p, RoundingMode::None);
222            term = neg_c(&term);
223            sum = sum.add(&term, p, RoundingMode::None);
224            if term_negligible(&term, p) {
225                break;
226            }
227        }
228        sum
229    }
230
231    fn bessel_y_nonint(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
232        let nupi = nu.mul(&pi_c(work_p, cc), work_p, RoundingMode::None);
233        let s = nupi.sin(work_p, RoundingMode::None, cc);
234        if term_negligible(&s, work_p) {
235            return nan_pair(Error::InvalidArgument);
236        }
237        let jp = self.bessel_j_at(nu, work_p, dest_p, cc);
238        let jm = self.bessel_j_at(&neg_c(nu), work_p, dest_p, cc);
239        let c = nupi.cos(work_p, RoundingMode::None, cc);
240        jp.mul(&c, work_p, RoundingMode::None)
241            .sub(&jm, work_p, RoundingMode::None)
242            .div(&s, work_p, RoundingMode::None)
243    }
244
245    fn bessel_y_int(&self, n: i32, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
246        let an = n.unsigned_abs();
247        let y = if use_bessel_series(self, dest_p) {
248            match an {
249                0 => self.bessel_y0_series(work_p, dest_p, cc),
250                1 => self.bessel_y1_series(work_p, dest_p, cc),
251                _ => self.bessel_y_recurrence(an, work_p, dest_p, cc),
252            }
253        } else {
254            let nu = ExactComplex::from_real(ExactNum::from_u32(an, work_p), work_p);
255            self.bessel_hankel_y(&nu, work_p, cc)
256        };
257        if n < 0 && an % 2 == 1 {
258            neg_c(&y)
259        } else {
260            y
261        }
262    }
263
264    fn bessel_y_recurrence(&self, n: u32, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
265        let mut ym2 = self.bessel_y0_series(work_p, dest_p, cc);
266        let mut ym1 = self.bessel_y1_series(work_p, dest_p, cc);
267        for m in 1..n {
268            let two_m = ExactComplex::from_real(ExactNum::from_u32(2 * m, work_p), work_p);
269            let ym = two_m
270                .div(self, work_p, RoundingMode::None)
271                .mul(&ym1, work_p, RoundingMode::None)
272                .sub(&ym2, work_p, RoundingMode::None);
273            ym2 = ym1;
274            ym1 = ym;
275        }
276        ym1
277    }
278
279    fn bessel_y0_series(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
280        let two_pi = two_c(work_p).div(&pi_c(work_p, cc), work_p, RoundingMode::None);
281        let half = self.mul(&half_c(work_p), work_p, RoundingMode::None);
282        let j0 = self.bessel_j_at(&ExactComplex::zero(work_p), work_p, dest_p, cc);
283        let g = ExactComplex::from_real(cc.euler_gamma(work_p, RoundingMode::None), work_p);
284        let prefix = g.add(
285            &half.ln(work_p, RoundingMode::None, cc),
286            work_p,
287            RoundingMode::None,
288        );
289        let z2 = half.mul(&half, work_p, RoundingMode::None);
290        let mut fact = ExactNum::from_u8(1, work_p);
291        let mut zk = ExactComplex::one(work_p);
292        let mut sum = ExactComplex::zero(work_p);
293        for m in 1..=series_term_cap(work_p) {
294            fact = fact.mul(
295                &ExactNum::from_u32(m as u32, work_p),
296                work_p,
297                RoundingMode::None,
298            );
299            zk = zk.mul(&z2, work_p, RoundingMode::None);
300            let h = harmonic(m as usize, work_p);
301            let den = fact.mul(&fact, work_p, RoundingMode::None);
302            let mut term = ExactComplex::from_real(h.div(&den, work_p, RoundingMode::None), work_p)
303                .mul(&zk, work_p, RoundingMode::None);
304            if m % 2 == 0 {
305                term = neg_c(&term);
306            }
307            sum = sum.add(&term, work_p, RoundingMode::None);
308            if term_negligible(&term, work_p) {
309                break;
310            }
311        }
312        two_pi.mul(
313            &prefix
314                .mul(&j0, work_p, RoundingMode::None)
315                .add(&sum, work_p, RoundingMode::None),
316            work_p,
317            RoundingMode::None,
318        )
319    }
320
321    fn bessel_y1_series(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
322        let nu1 = ExactComplex::one(work_p);
323        let two_pi = two_c(work_p).div(&pi_c(work_p, cc), work_p, RoundingMode::None);
324        let half = self.mul(&half_c(work_p), work_p, RoundingMode::None);
325        let j1 = self.bessel_j_at(&nu1, work_p, dest_p, cc);
326        let g = ExactComplex::from_real(cc.euler_gamma(work_p, RoundingMode::None), work_p);
327        let prefix = g.add(
328            &half.ln(work_p, RoundingMode::None, cc),
329            work_p,
330            RoundingMode::None,
331        );
332        let z2 = half.mul(&half, work_p, RoundingMode::None);
333        let mut kfact = ExactNum::from_u8(1, work_p);
334        let mut kp1fact = ExactNum::from_u8(1, work_p);
335        let mut zk = ExactComplex::one(work_p);
336        let mut sum = ExactComplex::zero(work_p);
337        for k in 0..=series_term_cap(work_p) {
338            let hk = harmonic(k as usize, work_p);
339            let rec = ExactNum::from_u8(1, work_p).div(
340                &ExactNum::from_u32((k + 1) as u32, work_p),
341                work_p,
342                RoundingMode::None,
343            );
344            let hkp1 = hk.add(&rec, work_p, RoundingMode::None);
345            let den = kfact.mul(&kp1fact, work_p, RoundingMode::None);
346            let mut term = ExactComplex::from_real(
347                hk.add(&hkp1, work_p, RoundingMode::None)
348                    .div(&den, work_p, RoundingMode::None),
349                work_p,
350            )
351            .mul(&zk, work_p, RoundingMode::None);
352            if k % 2 == 1 {
353                term = neg_c(&term);
354            }
355            sum = sum.add(&term, work_p, RoundingMode::None);
356            if k > 0 && term_negligible(&term, work_p) {
357                break;
358            }
359            let kp = k + 1;
360            kfact = kfact.mul(
361                &ExactNum::from_u32(kp as u32, work_p),
362                work_p,
363                RoundingMode::None,
364            );
365            kp1fact = kp1fact.mul(
366                &ExactNum::from_u32((kp + 1) as u32, work_p),
367                work_p,
368                RoundingMode::None,
369            );
370            zk = zk.mul(&z2, work_p, RoundingMode::None);
371        }
372        let a = two_pi.mul(
373            &prefix.mul(&j1, work_p, RoundingMode::None),
374            work_p,
375            RoundingMode::None,
376        );
377        let b = two_c(work_p).div(
378            &pi_c(work_p, cc).mul(self, work_p, RoundingMode::None),
379            work_p,
380            RoundingMode::None,
381        );
382        let c = self
383            .div(
384                &two_c(work_p).mul(&pi_c(work_p, cc), work_p, RoundingMode::None),
385                work_p,
386                RoundingMode::None,
387            )
388            .mul(&sum, work_p, RoundingMode::None);
389        a.sub(&b, work_p, RoundingMode::None)
390            .sub(&c, work_p, RoundingMode::None)
391    }
392
393    fn hankel_chi_omega(&self, nu: &Self, p: usize, cc: &mut Consts) -> (Self, Self) {
394        let two_nu_1 = two_c(p).mul(nu, p, RoundingMode::None).add(
395            &ExactComplex::one(p),
396            p,
397            RoundingMode::None,
398        );
399        let chi = self.sub(
400            &two_nu_1.mul(&pi_c(p, cc), p, RoundingMode::None).div(
401                &four_c(p),
402                p,
403                RoundingMode::None,
404            ),
405            p,
406            RoundingMode::None,
407        );
408        let two_over = two_c(p).div(
409            &pi_c(p, cc).mul(self, p, RoundingMode::None),
410            p,
411            RoundingMode::None,
412        );
413        let omega = two_over.sqrt(p, RoundingMode::None, cc);
414        (chi, omega)
415    }
416
417    fn hankel_pq(&self, nu: &Self, p: usize) -> (Self, Self) {
418        let two_nu = two_c(p).mul(nu, p, RoundingMode::None);
419        let mu = two_nu.mul(&two_nu, p, RoundingMode::None);
420        let eight_z = eight_c(p).mul(self, p, RoundingMode::None);
421        let mut prod = ExactComplex::one(p);
422        let mut kf = ExactNum::from_u8(1, p);
423        let mut pz = ExactComplex::one(p);
424        let mut psum = ExactComplex::one(p);
425        let mut qsum = ExactComplex::zero(p);
426        for k in 1..=series_term_cap(p) {
427            let odd = ExactComplex::from_real(ExactNum::from_u32((2 * k - 1) as u32, p), p);
428            let odd2 = odd.mul(&odd, p, RoundingMode::None);
429            prod = prod.mul(&mu.sub(&odd2, p, RoundingMode::None), p, RoundingMode::None);
430            kf = kf.mul(&ExactNum::from_u32(k as u32, p), p, RoundingMode::None);
431            pz = pz.mul(&eight_z, p, RoundingMode::None);
432            let term = prod.div(
433                &ExactComplex::from_real(kf.clone(), p).mul(&pz, p, RoundingMode::None),
434                p,
435                RoundingMode::None,
436            );
437            if k % 2 == 0 {
438                let signed = if (k / 2) % 2 == 1 { neg_c(&term) } else { term.clone() };
439                psum = psum.add(&signed, p, RoundingMode::None);
440            } else {
441                let signed = if ((k - 1) / 2) % 2 == 1 { neg_c(&term) } else { term.clone() };
442                qsum = qsum.add(&signed, p, RoundingMode::None);
443            }
444            if term_negligible(&term, p) {
445                break;
446            }
447        }
448        (psum, qsum)
449    }
450
451    fn bessel_hankel_j(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
452        let (chi, omega) = self.hankel_chi_omega(nu, p, cc);
453        let (pp, qq) = self.hankel_pq(nu, p);
454        let (sn, cs) = {
455            let s = chi.sin(p, RoundingMode::None, cc);
456            let c = chi.cos(p, RoundingMode::None, cc);
457            (s, c)
458        };
459        omega.mul(
460            &pp.mul(&cs, p, RoundingMode::None).sub(
461                &qq.mul(&sn, p, RoundingMode::None),
462                p,
463                RoundingMode::None,
464            ),
465            p,
466            RoundingMode::None,
467        )
468    }
469
470    fn bessel_hankel_y(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
471        let (chi, omega) = self.hankel_chi_omega(nu, p, cc);
472        let (pp, qq) = self.hankel_pq(nu, p);
473        let s = chi.sin(p, RoundingMode::None, cc);
474        let c = chi.cos(p, RoundingMode::None, cc);
475        omega.mul(
476            &pp.mul(&s, p, RoundingMode::None).add(
477                &qq.mul(&c, p, RoundingMode::None),
478                p,
479                RoundingMode::None,
480            ),
481            p,
482            RoundingMode::None,
483        )
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use super::*;
490    use crate::complex_special::neg_c;
491    use crate::complex_special::pi_c;
492    use crate::complex_special::two_c;
493
494    fn near(a: &ExactNum, b: &ExactNum, p: usize) -> bool {
495        let d = a.sub(b, p, RoundingMode::None).abs();
496        d.is_zero() || d.exponent().is_some_and(|e| e < -((p as i32) / 4))
497    }
498
499    fn cnear(a: &ExactComplex, b: &ExactComplex, p: usize) -> bool {
500        near(a.re(), b.re(), p) && near(a.im(), b.im(), p)
501    }
502
503    fn cnear_bits(a: &ExactComplex, b: &ExactComplex, p: usize, slack: i32) -> bool {
504        let dr = a.re().sub(b.re(), p, RoundingMode::None).abs();
505        let di = a.im().sub(b.im(), p, RoundingMode::None).abs();
506        (dr.is_zero() || dr.exponent().is_some_and(|e| e < -((p as i32) / slack)))
507            && (di.is_zero() || di.exponent().is_some_and(|e| e < -((p as i32) / slack)))
508    }
509
510    fn tiny(x: &ExactNum, p: usize) -> bool {
511        x.is_zero() || x.exponent().is_some_and(|e| e < -((p as i32) / 4))
512    }
513
514    #[test]
515    fn test_complex_bessel_golds() {
516        let p = 256;
517        let rm = RoundingMode::ToEven;
518        let mut cc = Consts::new().unwrap();
519
520        let one = ExactComplex::one(p);
521        let z1 = one.clone();
522        let nu0 = ExactComplex::zero(p);
523        let nu1 = ExactComplex::one(p);
524        let j0 = z1.bessel_j_nu(&nu0, p, rm, &mut cc);
525        let rj0 = ExactNum::from_u8(1, p).bessel_j_nu(&ExactNum::from_u8(0, p), p, rm, &mut cc);
526        assert!(near(j0.re(), &rj0, p));
527        assert!(tiny(j0.im(), p));
528        let j1 = z1.bessel_j_nu(&nu1, p, rm, &mut cc);
529        let rj1 = ExactNum::from_u8(1, p).bessel_j_nu(&ExactNum::from_u8(1, p), p, rm, &mut cc);
530        assert!(near(j1.re(), &rj1, p));
531        assert!(tiny(j1.im(), p));
532
533        let z = ExactComplex::new(ExactNum::from_u8(1, p), half_c(p).re().clone());
534        let jn = z.bessel_j_nu(&nu0, p, rm, &mut cc);
535        let yn1 = z.bessel_y(&nu1, p, rm, &mut cc);
536        let jn1 = z.bessel_j_nu(&nu1, p, rm, &mut cc);
537        let yn = z.bessel_y(&nu0, p, rm, &mut cc);
538        let lhs = jn.mul(&yn1, p, rm).sub(&jn1.mul(&yn, p, rm), p, rm);
539        let rhs = neg_c(&two_c(p)).div(&pi_c(p, &mut cc).mul(&z, p, rm), p, rm);
540        assert!(cnear_bits(&lhs, &rhs, p, 8));
541
542        let i0 = z1.bessel_i(&nu0, p, rm, &mut cc);
543        let ri0 = ExactNum::from_u8(1, p).bessel_i(&ExactNum::from_u8(0, p), p, rm, &mut cc);
544        assert!(near(i0.re(), &ri0, p));
545        assert!(tiny(i0.im(), p));
546
547        let k0 = z1.bessel_k(&nu0, p, rm, &mut cc);
548        let rk0 = ExactNum::from_u8(1, p).bessel_k(&ExactNum::from_u8(0, p), p, rm, &mut cc);
549        assert!(near(k0.re(), &rk0, p));
550        assert!(tiny(k0.im(), p));
551
552        let iz = ExactComplex::i(p).mul(&z, p, rm);
553        let j_iz = iz.bessel_j_nu(&nu0, p, rm, &mut cc);
554        let ln_i = ExactComplex::i(p).ln(p, rm, &mut cc);
555        let scale = neg_c(&nu0).mul(&ln_i, p, rm).exp(p, rm, &mut cc);
556        let via_j = scale.mul(&j_iz, p, rm);
557        let i_z = z.bessel_i(&nu0, p, rm, &mut cc);
558        assert!(cnear(&i_z, &via_j, p));
559
560        let h = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
561        let hc = ExactComplex::from_real(h, p);
562        let num = z.add(&hc, p, rm).bessel_j_nu(&nu0, p, rm, &mut cc).sub(
563            &z.sub(&hc, p, rm).bessel_j_nu(&nu0, p, rm, &mut cc),
564            p,
565            rm,
566        );
567        let deriv = num.div(&hc.mul(&two_c(p), p, rm), p, rm);
568        let expect = neg_c(&z.bessel_j_nu(&nu1, p, rm, &mut cc));
569        assert!(cnear_bits(&deriv, &expect, p, 8));
570
571        let half = half_c(p);
572        let z0 = ExactComplex::zero(p);
573        assert!(z0.bessel_j_nu(&half, p, rm, &mut cc).is_nan());
574
575        let eps = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
576        let above = ExactComplex::new(ExactNum::from_i8(-1, p), eps.clone());
577        let below = ExactComplex::new(
578            ExactNum::from_i8(-1, p),
579            ExactNum::from_i8(-1, p).mul(&eps, p, rm),
580        );
581        let ja = above.bessel_j_nu(&half, p, rm, &mut cc);
582        let jb = below.bessel_j_nu(&half, p, rm, &mut cc);
583        assert!(!cnear(&ja, &jb, p));
584        assert!(cnear_bits(&ja, &jb.conj(), p, 8) || !tiny(ja.im(), p) || !tiny(jb.im(), p));
585    }
586}