zenith-float-num 1.0.0

Software big-float kernel for zenith-float.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
//! Complex Bessel \(J_\nu,Y_\nu,I_\nu,K_\nu\).

use crate::common::util::round_p;
use crate::complex_special::half_c;
use crate::complex_special::nan_pair;
use crate::complex_special::neg_c;
use crate::complex_special::pi_c;
use crate::complex_special::series_term_cap;
use crate::complex_special::term_negligible;
use crate::complex_special::two_c;
use crate::complex_special::ziv_complex;
use crate::Consts;
use crate::Error;
use crate::ExactComplex;
use crate::ExactNum;
use crate::RoundingMode;

/// Use the power series when \(\lvert z\rvert\) is below this (and \(\lvert z\rvert^2\)
/// is large enough for the Hankel expansion only beyond destination precision).
const BESSEL_SERIES_THRESHOLD: u32 = 16;

/// Integers \(\lvert n\rvert\) recognized exactly for \(Y_n\) recurrence and \(z=0\).
const BESSEL_INTEGER_MAX: i32 = 64;

fn abs_below(z: &ExactComplex, bound: u32, p: usize) -> bool {
    let a = z.abs(p, RoundingMode::None);
    let b = ExactNum::from_u32(bound, p);
    matches!(a.cmp(&b), Some(c) if c < 0)
}

fn use_bessel_series(z: &ExactComplex, dest_p: usize) -> bool {
    if abs_below(z, BESSEL_SERIES_THRESHOLD, dest_p) {
        return true;
    }
    let az = z.abs(dest_p, RoundingMode::None);
    let az2 = az.mul(&az, dest_p, RoundingMode::None);
    let thresh = ExactNum::from_u32(dest_p.min(u32::MAX as usize) as u32, dest_p);
    matches!(az2.cmp(&thresh), Some(c) if c < 0)
}

fn integer_nu(nu: &ExactComplex, p: usize) -> Option<i32> {
    if !nu.im().is_zero() || !nu.re().is_int() {
        return None;
    }
    for n in -BESSEL_INTEGER_MAX..=BESSEL_INTEGER_MAX {
        let w = ExactNum::from_i32(n, p);
        if nu.re().cmp(&w) == Some(0) {
            return Some(n);
        }
    }
    None
}

fn harmonic(k: usize, p: usize) -> ExactNum {
    let mut h = ExactNum::new(p);
    for i in 1..=k {
        let t =
            ExactNum::from_u8(1, p).div(&ExactNum::from_u32(i as u32, p), p, RoundingMode::None);
        h = h.add(&t, p, RoundingMode::None);
    }
    h
}

fn four_c(p: usize) -> ExactComplex {
    ExactComplex::from_real(ExactNum::from_u8(4, p), p)
}

fn eight_c(p: usize) -> ExactComplex {
    ExactComplex::from_real(ExactNum::from_u8(8, p), p)
}

impl ExactComplex {
    /// \(J_\nu(z)\). Entire for integer \(\nu\); cut on \((-\infty,0]\) otherwise.
    /// \(z=0\) with non-integer \(\nu\) → NaN.
    ///
    /// # Precision
    ///
    /// - Algorithm: series for `|z| < BESSEL_SERIES_THRESHOLD` (`16`); Hankel otherwise. Integer `|n| ≤ BESSEL_INTEGER_MAX` (`64`).
    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
    /// - MPFR oracle: no.
    pub fn bessel_j_nu(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
        if self.is_nan() || nu.is_nan() {
            return nan_pair(Error::InvalidArgument);
        }
        let dest = round_p(p);
        ziv_complex(dest, rm, |pw| self.bessel_j_at(nu, pw, dest, cc))
    }

    /// \(Y_\nu(z)\). Cut on \((-\infty,0]\); \(z=0\) → NaN.
    ///
    /// # Precision
    ///
    /// - Algorithm: from \(J_ν\); `BESSEL_SERIES_THRESHOLD = 16`.
    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
    /// - MPFR oracle: no.
    pub fn bessel_y(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
        if self.is_nan() || nu.is_nan() {
            return nan_pair(Error::InvalidArgument);
        }
        if self.re().is_zero() && self.im().is_zero() {
            return nan_pair(Error::InvalidArgument);
        }
        let dest = round_p(p);
        ziv_complex(dest, rm, |pw| self.bessel_y_at(nu, pw, dest, cc))
    }

    /// \(I_\nu(z)=i^{-\nu}J_\nu(iz)\). Same cut rules as \(J_\nu\).
    ///
    /// # Precision
    ///
    /// - Algorithm: via [`Self::bessel_j_nu`]; `BESSEL_SERIES_THRESHOLD = 16`.
    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
    /// - MPFR oracle: no.
    pub fn bessel_i(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
        if self.is_nan() || nu.is_nan() {
            return nan_pair(Error::InvalidArgument);
        }
        let dest = round_p(p);
        ziv_complex(dest, rm, |pw| self.bessel_i_at(nu, pw, dest, cc))
    }

    /// \(K_\nu(z)=(\pi/2)\,i^{\nu+1}H_\nu^{(1)}(iz)\). Cut on \((-\infty,0]\); \(z=0\) → NaN.
    ///
    /// # Precision
    ///
    /// - Algorithm: Hankel of \(iz\); `BESSEL_SERIES_THRESHOLD = 16`.
    /// - Bound: Ziv on each part (`MAX_PREC_RETRY`).
    /// - MPFR oracle: no.
    pub fn bessel_k(&self, nu: &Self, p: usize, rm: RoundingMode, cc: &mut Consts) -> Self {
        if self.is_nan() || nu.is_nan() {
            return nan_pair(Error::InvalidArgument);
        }
        if self.re().is_zero() && self.im().is_zero() {
            return nan_pair(Error::InvalidArgument);
        }
        let dest = round_p(p);
        ziv_complex(dest, rm, |pw| self.bessel_k_at(nu, pw, dest, cc))
    }

    fn bessel_j_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        if self.re().is_zero() && self.im().is_zero() {
            return match integer_nu(nu, work_p) {
                Some(0) => ExactComplex::one(work_p),
                Some(_) => ExactComplex::zero(work_p),
                None => nan_pair(Error::InvalidArgument),
            };
        }
        if use_bessel_series(self, dest_p) {
            self.bessel_j_series(nu, work_p, cc)
        } else {
            self.bessel_hankel_j(nu, work_p, cc)
        }
    }

    fn bessel_y_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        if let Some(n) = integer_nu(nu, work_p) {
            return self.bessel_y_int(n, work_p, dest_p, cc);
        }
        if use_bessel_series(self, dest_p) {
            self.bessel_y_nonint(nu, work_p, dest_p, cc)
        } else {
            self.bessel_hankel_y(nu, work_p, cc)
        }
    }

    fn bessel_i_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        if self.re().is_zero() && self.im().is_zero() {
            return match integer_nu(nu, work_p) {
                Some(0) => ExactComplex::one(work_p),
                Some(n) if n > 0 => ExactComplex::zero(work_p),
                _ => nan_pair(Error::InvalidArgument),
            };
        }
        let iz = ExactComplex::i(work_p).mul(self, work_p, RoundingMode::None);
        let j = iz.bessel_j_at(nu, work_p, dest_p, cc);
        let ln_i = ExactComplex::i(work_p).ln(work_p, RoundingMode::None, cc);
        let scale =
            neg_c(nu)
                .mul(&ln_i, work_p, RoundingMode::None)
                .exp(work_p, RoundingMode::None, cc);
        scale.mul(&j, work_p, RoundingMode::None)
    }

    fn bessel_k_at(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let iz = ExactComplex::i(work_p).mul(self, work_p, RoundingMode::None);
        let j = iz.bessel_j_at(nu, work_p, dest_p, cc);
        let y = iz.bessel_y_at(nu, work_p, dest_p, cc);
        let h1 = j.add(
            &ExactComplex::i(work_p).mul(&y, work_p, RoundingMode::None),
            work_p,
            RoundingMode::None,
        );
        let ln_i = ExactComplex::i(work_p).ln(work_p, RoundingMode::None, cc);
        let nu_p1 = nu.add(&ExactComplex::one(work_p), work_p, RoundingMode::None);
        let i_pow =
            nu_p1
                .mul(&ln_i, work_p, RoundingMode::None)
                .exp(work_p, RoundingMode::None, cc);
        let half_pi = pi_c(work_p, cc).mul(&half_c(work_p), work_p, RoundingMode::None);
        half_pi
            .mul(&i_pow, work_p, RoundingMode::None)
            .mul(&h1, work_p, RoundingMode::None)
    }

    fn bessel_j_series(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
        let half = self.mul(&half_c(p), p, RoundingMode::None);
        let pow = half.pow(nu, p, RoundingMode::None, cc);
        let g =
            nu.add(&ExactComplex::one(p), p, RoundingMode::None)
                .gamma(p, RoundingMode::None, cc);
        let mut term = pow.div(&g, p, RoundingMode::None);
        let mut sum = term.clone();
        let hh = half.mul(&half, p, RoundingMode::None);
        for k in 1..=series_term_cap(p) {
            let kk = ExactComplex::from_real(ExactNum::from_u32(k as u32, p), p);
            let den = kk
                .add(nu, p, RoundingMode::None)
                .mul(&kk, p, RoundingMode::None);
            term = term
                .mul(&hh, p, RoundingMode::None)
                .div(&den, p, RoundingMode::None);
            term = neg_c(&term);
            sum = sum.add(&term, p, RoundingMode::None);
            if term_negligible(&term, p) {
                break;
            }
        }
        sum
    }

    fn bessel_y_nonint(&self, nu: &Self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let nupi = nu.mul(&pi_c(work_p, cc), work_p, RoundingMode::None);
        let s = nupi.sin(work_p, RoundingMode::None, cc);
        if term_negligible(&s, work_p) {
            return nan_pair(Error::InvalidArgument);
        }
        let jp = self.bessel_j_at(nu, work_p, dest_p, cc);
        let jm = self.bessel_j_at(&neg_c(nu), work_p, dest_p, cc);
        let c = nupi.cos(work_p, RoundingMode::None, cc);
        jp.mul(&c, work_p, RoundingMode::None)
            .sub(&jm, work_p, RoundingMode::None)
            .div(&s, work_p, RoundingMode::None)
    }

    fn bessel_y_int(&self, n: i32, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let an = n.unsigned_abs();
        let y = if use_bessel_series(self, dest_p) {
            match an {
                0 => self.bessel_y0_series(work_p, dest_p, cc),
                1 => self.bessel_y1_series(work_p, dest_p, cc),
                _ => self.bessel_y_recurrence(an, work_p, dest_p, cc),
            }
        } else {
            let nu = ExactComplex::from_real(ExactNum::from_u32(an, work_p), work_p);
            self.bessel_hankel_y(&nu, work_p, cc)
        };
        if n < 0 && an % 2 == 1 {
            neg_c(&y)
        } else {
            y
        }
    }

    fn bessel_y_recurrence(&self, n: u32, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let mut ym2 = self.bessel_y0_series(work_p, dest_p, cc);
        let mut ym1 = self.bessel_y1_series(work_p, dest_p, cc);
        for m in 1..n {
            let two_m = ExactComplex::from_real(ExactNum::from_u32(2 * m, work_p), work_p);
            let ym = two_m
                .div(self, work_p, RoundingMode::None)
                .mul(&ym1, work_p, RoundingMode::None)
                .sub(&ym2, work_p, RoundingMode::None);
            ym2 = ym1;
            ym1 = ym;
        }
        ym1
    }

    fn bessel_y0_series(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let two_pi = two_c(work_p).div(&pi_c(work_p, cc), work_p, RoundingMode::None);
        let half = self.mul(&half_c(work_p), work_p, RoundingMode::None);
        let j0 = self.bessel_j_at(&ExactComplex::zero(work_p), work_p, dest_p, cc);
        let g = ExactComplex::from_real(cc.euler_gamma(work_p, RoundingMode::None), work_p);
        let prefix = g.add(
            &half.ln(work_p, RoundingMode::None, cc),
            work_p,
            RoundingMode::None,
        );
        let z2 = half.mul(&half, work_p, RoundingMode::None);
        let mut fact = ExactNum::from_u8(1, work_p);
        let mut zk = ExactComplex::one(work_p);
        let mut sum = ExactComplex::zero(work_p);
        for m in 1..=series_term_cap(work_p) {
            fact = fact.mul(
                &ExactNum::from_u32(m as u32, work_p),
                work_p,
                RoundingMode::None,
            );
            zk = zk.mul(&z2, work_p, RoundingMode::None);
            let h = harmonic(m as usize, work_p);
            let den = fact.mul(&fact, work_p, RoundingMode::None);
            let mut term = ExactComplex::from_real(h.div(&den, work_p, RoundingMode::None), work_p)
                .mul(&zk, work_p, RoundingMode::None);
            if m % 2 == 0 {
                term = neg_c(&term);
            }
            sum = sum.add(&term, work_p, RoundingMode::None);
            if term_negligible(&term, work_p) {
                break;
            }
        }
        two_pi.mul(
            &prefix
                .mul(&j0, work_p, RoundingMode::None)
                .add(&sum, work_p, RoundingMode::None),
            work_p,
            RoundingMode::None,
        )
    }

    fn bessel_y1_series(&self, work_p: usize, dest_p: usize, cc: &mut Consts) -> Self {
        let nu1 = ExactComplex::one(work_p);
        let two_pi = two_c(work_p).div(&pi_c(work_p, cc), work_p, RoundingMode::None);
        let half = self.mul(&half_c(work_p), work_p, RoundingMode::None);
        let j1 = self.bessel_j_at(&nu1, work_p, dest_p, cc);
        let g = ExactComplex::from_real(cc.euler_gamma(work_p, RoundingMode::None), work_p);
        let prefix = g.add(
            &half.ln(work_p, RoundingMode::None, cc),
            work_p,
            RoundingMode::None,
        );
        let z2 = half.mul(&half, work_p, RoundingMode::None);
        let mut kfact = ExactNum::from_u8(1, work_p);
        let mut kp1fact = ExactNum::from_u8(1, work_p);
        let mut zk = ExactComplex::one(work_p);
        let mut sum = ExactComplex::zero(work_p);
        for k in 0..=series_term_cap(work_p) {
            let hk = harmonic(k as usize, work_p);
            let rec = ExactNum::from_u8(1, work_p).div(
                &ExactNum::from_u32((k + 1) as u32, work_p),
                work_p,
                RoundingMode::None,
            );
            let hkp1 = hk.add(&rec, work_p, RoundingMode::None);
            let den = kfact.mul(&kp1fact, work_p, RoundingMode::None);
            let mut term = ExactComplex::from_real(
                hk.add(&hkp1, work_p, RoundingMode::None)
                    .div(&den, work_p, RoundingMode::None),
                work_p,
            )
            .mul(&zk, work_p, RoundingMode::None);
            if k % 2 == 1 {
                term = neg_c(&term);
            }
            sum = sum.add(&term, work_p, RoundingMode::None);
            if k > 0 && term_negligible(&term, work_p) {
                break;
            }
            let kp = k + 1;
            kfact = kfact.mul(
                &ExactNum::from_u32(kp as u32, work_p),
                work_p,
                RoundingMode::None,
            );
            kp1fact = kp1fact.mul(
                &ExactNum::from_u32((kp + 1) as u32, work_p),
                work_p,
                RoundingMode::None,
            );
            zk = zk.mul(&z2, work_p, RoundingMode::None);
        }
        let a = two_pi.mul(
            &prefix.mul(&j1, work_p, RoundingMode::None),
            work_p,
            RoundingMode::None,
        );
        let b = two_c(work_p).div(
            &pi_c(work_p, cc).mul(self, work_p, RoundingMode::None),
            work_p,
            RoundingMode::None,
        );
        let c = self
            .div(
                &two_c(work_p).mul(&pi_c(work_p, cc), work_p, RoundingMode::None),
                work_p,
                RoundingMode::None,
            )
            .mul(&sum, work_p, RoundingMode::None);
        a.sub(&b, work_p, RoundingMode::None)
            .sub(&c, work_p, RoundingMode::None)
    }

    fn hankel_chi_omega(&self, nu: &Self, p: usize, cc: &mut Consts) -> (Self, Self) {
        let two_nu_1 = two_c(p).mul(nu, p, RoundingMode::None).add(
            &ExactComplex::one(p),
            p,
            RoundingMode::None,
        );
        let chi = self.sub(
            &two_nu_1.mul(&pi_c(p, cc), p, RoundingMode::None).div(
                &four_c(p),
                p,
                RoundingMode::None,
            ),
            p,
            RoundingMode::None,
        );
        let two_over = two_c(p).div(
            &pi_c(p, cc).mul(self, p, RoundingMode::None),
            p,
            RoundingMode::None,
        );
        let omega = two_over.sqrt(p, RoundingMode::None, cc);
        (chi, omega)
    }

    fn hankel_pq(&self, nu: &Self, p: usize) -> (Self, Self) {
        let two_nu = two_c(p).mul(nu, p, RoundingMode::None);
        let mu = two_nu.mul(&two_nu, p, RoundingMode::None);
        let eight_z = eight_c(p).mul(self, p, RoundingMode::None);
        let mut prod = ExactComplex::one(p);
        let mut kf = ExactNum::from_u8(1, p);
        let mut pz = ExactComplex::one(p);
        let mut psum = ExactComplex::one(p);
        let mut qsum = ExactComplex::zero(p);
        for k in 1..=series_term_cap(p) {
            let odd = ExactComplex::from_real(ExactNum::from_u32((2 * k - 1) as u32, p), p);
            let odd2 = odd.mul(&odd, p, RoundingMode::None);
            prod = prod.mul(&mu.sub(&odd2, p, RoundingMode::None), p, RoundingMode::None);
            kf = kf.mul(&ExactNum::from_u32(k as u32, p), p, RoundingMode::None);
            pz = pz.mul(&eight_z, p, RoundingMode::None);
            let term = prod.div(
                &ExactComplex::from_real(kf.clone(), p).mul(&pz, p, RoundingMode::None),
                p,
                RoundingMode::None,
            );
            if k % 2 == 0 {
                let signed = if (k / 2) % 2 == 1 { neg_c(&term) } else { term.clone() };
                psum = psum.add(&signed, p, RoundingMode::None);
            } else {
                let signed = if ((k - 1) / 2) % 2 == 1 { neg_c(&term) } else { term.clone() };
                qsum = qsum.add(&signed, p, RoundingMode::None);
            }
            if term_negligible(&term, p) {
                break;
            }
        }
        (psum, qsum)
    }

    fn bessel_hankel_j(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
        let (chi, omega) = self.hankel_chi_omega(nu, p, cc);
        let (pp, qq) = self.hankel_pq(nu, p);
        let (sn, cs) = {
            let s = chi.sin(p, RoundingMode::None, cc);
            let c = chi.cos(p, RoundingMode::None, cc);
            (s, c)
        };
        omega.mul(
            &pp.mul(&cs, p, RoundingMode::None).sub(
                &qq.mul(&sn, p, RoundingMode::None),
                p,
                RoundingMode::None,
            ),
            p,
            RoundingMode::None,
        )
    }

    fn bessel_hankel_y(&self, nu: &Self, p: usize, cc: &mut Consts) -> Self {
        let (chi, omega) = self.hankel_chi_omega(nu, p, cc);
        let (pp, qq) = self.hankel_pq(nu, p);
        let s = chi.sin(p, RoundingMode::None, cc);
        let c = chi.cos(p, RoundingMode::None, cc);
        omega.mul(
            &pp.mul(&s, p, RoundingMode::None).add(
                &qq.mul(&c, p, RoundingMode::None),
                p,
                RoundingMode::None,
            ),
            p,
            RoundingMode::None,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::complex_special::neg_c;
    use crate::complex_special::pi_c;
    use crate::complex_special::two_c;

    fn near(a: &ExactNum, b: &ExactNum, p: usize) -> bool {
        let d = a.sub(b, p, RoundingMode::None).abs();
        d.is_zero() || d.exponent().is_some_and(|e| e < -((p as i32) / 4))
    }

    fn cnear(a: &ExactComplex, b: &ExactComplex, p: usize) -> bool {
        near(a.re(), b.re(), p) && near(a.im(), b.im(), p)
    }

    fn cnear_bits(a: &ExactComplex, b: &ExactComplex, p: usize, slack: i32) -> bool {
        let dr = a.re().sub(b.re(), p, RoundingMode::None).abs();
        let di = a.im().sub(b.im(), p, RoundingMode::None).abs();
        (dr.is_zero() || dr.exponent().is_some_and(|e| e < -((p as i32) / slack)))
            && (di.is_zero() || di.exponent().is_some_and(|e| e < -((p as i32) / slack)))
    }

    fn tiny(x: &ExactNum, p: usize) -> bool {
        x.is_zero() || x.exponent().is_some_and(|e| e < -((p as i32) / 4))
    }

    #[test]
    fn test_complex_bessel_golds() {
        let p = 256;
        let rm = RoundingMode::ToEven;
        let mut cc = Consts::new().unwrap();

        let one = ExactComplex::one(p);
        let z1 = one.clone();
        let nu0 = ExactComplex::zero(p);
        let nu1 = ExactComplex::one(p);
        let j0 = z1.bessel_j_nu(&nu0, p, rm, &mut cc);
        let rj0 = ExactNum::from_u8(1, p).bessel_j_nu(&ExactNum::from_u8(0, p), p, rm, &mut cc);
        assert!(near(j0.re(), &rj0, p));
        assert!(tiny(j0.im(), p));
        let j1 = z1.bessel_j_nu(&nu1, p, rm, &mut cc);
        let rj1 = ExactNum::from_u8(1, p).bessel_j_nu(&ExactNum::from_u8(1, p), p, rm, &mut cc);
        assert!(near(j1.re(), &rj1, p));
        assert!(tiny(j1.im(), p));

        let z = ExactComplex::new(ExactNum::from_u8(1, p), half_c(p).re().clone());
        let jn = z.bessel_j_nu(&nu0, p, rm, &mut cc);
        let yn1 = z.bessel_y(&nu1, p, rm, &mut cc);
        let jn1 = z.bessel_j_nu(&nu1, p, rm, &mut cc);
        let yn = z.bessel_y(&nu0, p, rm, &mut cc);
        let lhs = jn.mul(&yn1, p, rm).sub(&jn1.mul(&yn, p, rm), p, rm);
        let rhs = neg_c(&two_c(p)).div(&pi_c(p, &mut cc).mul(&z, p, rm), p, rm);
        assert!(cnear_bits(&lhs, &rhs, p, 8));

        let i0 = z1.bessel_i(&nu0, p, rm, &mut cc);
        let ri0 = ExactNum::from_u8(1, p).bessel_i(&ExactNum::from_u8(0, p), p, rm, &mut cc);
        assert!(near(i0.re(), &ri0, p));
        assert!(tiny(i0.im(), p));

        let k0 = z1.bessel_k(&nu0, p, rm, &mut cc);
        let rk0 = ExactNum::from_u8(1, p).bessel_k(&ExactNum::from_u8(0, p), p, rm, &mut cc);
        assert!(near(k0.re(), &rk0, p));
        assert!(tiny(k0.im(), p));

        let iz = ExactComplex::i(p).mul(&z, p, rm);
        let j_iz = iz.bessel_j_nu(&nu0, p, rm, &mut cc);
        let ln_i = ExactComplex::i(p).ln(p, rm, &mut cc);
        let scale = neg_c(&nu0).mul(&ln_i, p, rm).exp(p, rm, &mut cc);
        let via_j = scale.mul(&j_iz, p, rm);
        let i_z = z.bessel_i(&nu0, p, rm, &mut cc);
        assert!(cnear(&i_z, &via_j, p));

        let h = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
        let hc = ExactComplex::from_real(h, p);
        let num = z.add(&hc, p, rm).bessel_j_nu(&nu0, p, rm, &mut cc).sub(
            &z.sub(&hc, p, rm).bessel_j_nu(&nu0, p, rm, &mut cc),
            p,
            rm,
        );
        let deriv = num.div(&hc.mul(&two_c(p), p, rm), p, rm);
        let expect = neg_c(&z.bessel_j_nu(&nu1, p, rm, &mut cc));
        assert!(cnear_bits(&deriv, &expect, p, 8));

        let half = half_c(p);
        let z0 = ExactComplex::zero(p);
        assert!(z0.bessel_j_nu(&half, p, rm, &mut cc).is_nan());

        let eps = ExactNum::from_u8(2, p).powsi(-((p as isize) / 8), p, rm);
        let above = ExactComplex::new(ExactNum::from_i8(-1, p), eps.clone());
        let below = ExactComplex::new(
            ExactNum::from_i8(-1, p),
            ExactNum::from_i8(-1, p).mul(&eps, p, rm),
        );
        let ja = above.bessel_j_nu(&half, p, rm, &mut cc);
        let jb = below.bessel_j_nu(&half, p, rm, &mut cc);
        assert!(!cnear(&ja, &jb, p));
        assert!(cnear_bits(&ja, &jb.conj(), p, 8) || !tiny(ja.im(), p) || !tiny(jb.im(), p));
    }
}