Skip to main content

zenith_float_num/
quadrature.rs

1//! Gauss and tanh–sinh quadrature on [`ExactNum`].
2
3use crate::defs::WORD_BIT_SIZE;
4use crate::Consts;
5use crate::ExactNum;
6use crate::RoundingMode;
7use alloc::vec::Vec;
8
9/// Maximum number of Gauss nodes. Larger `n` is `None`.
10pub const QUADRATURE_MAX_NODES: usize = 64;
11
12/// Maximum tanh–sinh step halvings after the precision-based `h`.
13pub const TANH_SINH_LEVELS_MAX: usize = 8;
14
15/// Newton sweeps per Gauss root.
16const QUADRATURE_NEWTON_MAX: usize = 64;
17
18/// Grid points per expected root when isolating Laguerre / Hermite zeros.
19const QUADRATURE_BRACKET_MUL: usize = 8;
20
21/// Bisection steps inside a sign-change bracket before Newton.
22const QUADRATURE_BISECT_STEPS: usize = 16;
23
24/// Hard cap on tanh–sinh sample index `|k|`.
25const TANH_SINH_K_MAX: usize = 512;
26
27fn work_p(p: usize) -> usize {
28    p.saturating_add(WORD_BIT_SIZE)
29}
30
31/// Extra word so `1-x²` stays nonzero at tanh–sinh nodes until the Jacobian
32/// is smaller than a unit in the last place of a `p`-bit result.
33fn work_p_de(p: usize) -> usize {
34    p.saturating_mul(2).saturating_add(WORD_BIT_SIZE)
35}
36
37fn finite(x: &ExactNum) -> bool {
38    !x.is_nan() && !x.is_inf()
39}
40
41fn tiny(p: usize, extra: i32) -> ExactNum {
42    let e = (p as i32).saturating_sub(extra).saturating_neg();
43    ExactNum::from_u8(1, p).ldexp(e, p, RoundingMode::ToEven)
44}
45
46fn below(x: &ExactNum, bound: &ExactNum) -> bool {
47    x.is_zero() || x.cmp(bound) == Some(-1)
48}
49
50fn factorial(n: usize, p: usize, rm: RoundingMode) -> ExactNum {
51    let mut acc = ExactNum::from_u8(1, p);
52    for k in 2..=n {
53        acc = acc.mul(&ExactNum::from_u32(k as u32, p), p, rm);
54    }
55    acc
56}
57
58/// Newton for `f(x)=0` with analytic `df`, starting at `x0`.
59fn newton<F, D>(
60    mut f: F,
61    mut df: D,
62    x0: ExactNum,
63    p: usize,
64    rm: RoundingMode,
65    cc: &mut Consts,
66) -> Option<ExactNum>
67where
68    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
69    D: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
70{
71    let guard = tiny(p, 8);
72    let mut x = x0;
73    for _ in 0..QUADRATURE_NEWTON_MAX {
74        let y = f(&x, p, rm, cc);
75        let d = df(&x, p, rm, cc);
76        if !finite(&y) || !finite(&d) || d.is_zero() {
77            return None;
78        }
79        let step = y.div(&d, p, rm);
80        x = x.sub(&step, p, rm);
81        if !finite(&x) {
82            return None;
83        }
84        if below(&step.abs(), &guard) {
85            return Some(x);
86        }
87    }
88    None
89}
90
91fn bisect_zero<F>(
92    mut f: F,
93    mut a: ExactNum,
94    mut b: ExactNum,
95    p: usize,
96    rm: RoundingMode,
97    cc: &mut Consts,
98) -> Option<ExactNum>
99where
100    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
101{
102    let fa0 = f(&a, p, rm, cc);
103    let fb0 = f(&b, p, rm, cc);
104    if !finite(&fa0) || !finite(&fb0) {
105        return None;
106    }
107    if fa0.is_zero() {
108        return Some(a);
109    }
110    if fb0.is_zero() {
111        return Some(b);
112    }
113    if fa0.is_positive() == fb0.is_positive() {
114        return None;
115    }
116    let two = ExactNum::from_u8(2, p);
117    for _ in 0..QUADRATURE_BISECT_STEPS {
118        let m = a.add(&b, p, rm).div(&two, p, rm);
119        let fm = f(&m, p, rm, cc);
120        if !finite(&fm) {
121            return None;
122        }
123        if fm.is_zero() {
124            return Some(m);
125        }
126        if fm.is_positive() == fa0.is_positive() {
127            a = m;
128        } else {
129            b = m;
130        }
131    }
132    Some(a.add(&b, p, rm).div(&two, p, rm))
133}
134
135fn isolate_positive<F>(
136    mut f: F,
137    lo: &ExactNum,
138    hi: &ExactNum,
139    want: usize,
140    p: usize,
141    rm: RoundingMode,
142    cc: &mut Consts,
143) -> Option<Vec<(ExactNum, ExactNum)>>
144where
145    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
146{
147    if want == 0 {
148        return Some(Vec::new());
149    }
150    let n_grid = want
151        .saturating_mul(QUADRATURE_BRACKET_MUL)
152        .saturating_add(4);
153    let step = hi
154        .sub(lo, p, rm)
155        .div(&ExactNum::from_u32(n_grid as u32, p), p, rm);
156    let mut prev_x = lo.clone();
157    let mut prev_f = f(lo, p, rm, cc);
158    if !finite(&prev_f) {
159        return None;
160    }
161    let mut out = Vec::new();
162    for i in 1..=n_grid {
163        let x = if i == n_grid {
164            hi.clone()
165        } else {
166            lo.add(&step.mul(&ExactNum::from_u32(i as u32, p), p, rm), p, rm)
167        };
168        let y = f(&x, p, rm, cc);
169        if !finite(&y) {
170            return None;
171        }
172        if prev_f.is_zero() {
173            out.push((prev_x.clone(), x.clone()));
174        } else if y.is_zero() || prev_f.is_positive() != y.is_positive() {
175            out.push((prev_x, x.clone()));
176        }
177        if out.len() == want {
178            return Some(out);
179        }
180        prev_x = x;
181        prev_f = y;
182    }
183    if out.len() == want {
184        Some(out)
185    } else {
186        None
187    }
188}
189
190fn map_ab(xi: &ExactNum, a: &ExactNum, b: &ExactNum, p: usize, rm: RoundingMode) -> ExactNum {
191    let two = ExactNum::from_u8(2, p);
192    let mid = a.add(b, p, rm).div(&two, p, rm);
193    let half = b.sub(a, p, rm).div(&two, p, rm);
194    mid.add(&half.mul(xi, p, rm), p, rm)
195}
196
197fn gauss_legendre_nodes(
198    n: usize,
199    p: usize,
200    _rm: RoundingMode,
201    cc: &mut Consts,
202) -> Option<Vec<(ExactNum, ExactNum)>> {
203    if n == 0 || n > QUADRATURE_MAX_NODES {
204        return None;
205    }
206    let wrk = work_p(p);
207    let nu = n as u32;
208    let pi = cc.pi(wrk, RoundingMode::None);
209    let den = ExactNum::from_u32((4 * n + 2) as u32, wrk);
210    let n_f = ExactNum::from_u32(nu, wrk);
211    let one = ExactNum::from_u8(1, wrk);
212    let two = ExactNum::from_u8(2, wrk);
213    let mut nodes = Vec::with_capacity(n);
214    for k in 1..=n {
215        let num = ExactNum::from_u32((4 * k - 1) as u32, wrk);
216        let theta = pi
217            .mul(&num, wrk, RoundingMode::None)
218            .div(&den, wrk, RoundingMode::None);
219        let x0 = theta.cos(wrk, RoundingMode::None, cc);
220        let x = newton(
221            |x, p, rm, _cc| x.legendre_p(nu, p, rm),
222            |x, p, rm, _cc| {
223                let pn = x.legendre_p(nu, p, rm);
224                let pnm = if nu == 0 { ExactNum::new(p) } else { x.legendre_p(nu - 1, p, rm) };
225                let d = one.sub(&x.mul(x, p, rm), p, rm);
226                n_f.mul(&pnm.sub(&x.mul(&pn, p, rm), p, rm), p, rm)
227                    .div(&d, p, rm)
228            },
229            x0,
230            wrk,
231            RoundingMode::None,
232            cc,
233        )?;
234        let pn = x.legendre_p(nu, wrk, RoundingMode::None);
235        let pnm = if nu == 0 {
236            ExactNum::new(wrk)
237        } else {
238            x.legendre_p(nu - 1, wrk, RoundingMode::None)
239        };
240        let d = one.sub(&x.mul(&x, wrk, RoundingMode::None), wrk, RoundingMode::None);
241        let dp = n_f
242            .mul(
243                &pnm.sub(
244                    &x.mul(&pn, wrk, RoundingMode::None),
245                    wrk,
246                    RoundingMode::None,
247                ),
248                wrk,
249                RoundingMode::None,
250            )
251            .div(&d, wrk, RoundingMode::None);
252        let w = two.div(
253            &d.mul(
254                &dp.mul(&dp, wrk, RoundingMode::None),
255                wrk,
256                RoundingMode::None,
257            ),
258            wrk,
259            RoundingMode::None,
260        );
261        if !finite(&x) || !finite(&w) {
262            return None;
263        }
264        nodes.push((x, w));
265    }
266    Some(nodes)
267}
268
269/// Gauss–Legendre quadrature of `f` on `[a, b]` with `n` nodes.
270///
271/// Nodes are roots of `P_n`; weights `w_i = 2 / ((1-x_i²) [P_n'(x_i)]²)`.
272/// Exact for polynomials of degree `≤ 2n−1`. `None` if `n` is 0 or greater
273/// than [`QUADRATURE_MAX_NODES`], the interval is not a finite `a < b`, or
274/// a root fails to converge.
275pub fn gauss_legendre<F>(
276    mut f: F,
277    a: &ExactNum,
278    b: &ExactNum,
279    n: usize,
280    p: usize,
281    rm: RoundingMode,
282    cc: &mut Consts,
283) -> Option<ExactNum>
284where
285    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
286{
287    if !finite(a) || !finite(b) || a.cmp(b) != Some(-1) {
288        return None;
289    }
290    let wrk = work_p(p);
291    let nodes = gauss_legendre_nodes(n, wrk, RoundingMode::None, cc)?;
292    let two = ExactNum::from_u8(2, wrk);
293    let half = b
294        .sub(a, wrk, RoundingMode::None)
295        .div(&two, wrk, RoundingMode::None);
296    let mut acc = ExactNum::new(wrk);
297    for (xi, wi) in &nodes {
298        let x = map_ab(xi, a, b, wrk, RoundingMode::None);
299        let y = f(&x, wrk, RoundingMode::None, cc);
300        if !finite(&y) {
301            return None;
302        }
303        acc = acc.add(
304            &wi.mul(&y, wrk, RoundingMode::None),
305            wrk,
306            RoundingMode::None,
307        );
308    }
309    let mut out = half.mul(&acc, wrk, RoundingMode::None);
310    let _ = out.set_precision(p, rm);
311    Some(out)
312}
313
314fn tanh_sinh_sum<F>(
315    mut f: F,
316    a: &ExactNum,
317    b: &ExactNum,
318    h: &ExactNum,
319    p: usize,
320    rm: RoundingMode,
321    cc: &mut Consts,
322) -> Option<ExactNum>
323where
324    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
325{
326    let wrk = work_p_de(p);
327    let two = ExactNum::from_u8(2, wrk);
328    let half = b
329        .sub(a, wrk, RoundingMode::None)
330        .div(&two, wrk, RoundingMode::None);
331    let mid = a
332        .add(b, wrk, RoundingMode::None)
333        .div(&two, wrk, RoundingMode::None);
334    let pi = cc.pi(wrk, RoundingMode::None);
335    let half_pi = pi.div(&two, wrk, RoundingMode::None);
336    let wmin = tiny(wrk, 8);
337    let mut acc = ExactNum::new(wrk);
338    for k in 0..TANH_SINH_K_MAX {
339        let t = h.mul(&ExactNum::from_u32(k as u32, wrk), wrk, RoundingMode::None);
340        let (sh, ch) = t.sinh_cosh(wrk, RoundingMode::None, cc);
341        let u = half_pi.mul(&sh, wrk, RoundingMode::None);
342        let (su, cu) = u.sinh_cosh(wrk, RoundingMode::None, cc);
343        if !finite(&cu) || cu.is_zero() {
344            break;
345        }
346        let xi = su.div(&cu, wrk, RoundingMode::None);
347        let w = half_pi.mul(&ch, wrk, RoundingMode::None).div(
348            &cu.mul(&cu, wrk, RoundingMode::None),
349            wrk,
350            RoundingMode::None,
351        );
352        if !finite(&w) {
353            break;
354        }
355        if k > 0 && below(&w.abs(), &wmin) {
356            break;
357        }
358        let mut eval = |s: &ExactNum| {
359            let x = mid.add(
360                &half.mul(s, wrk, RoundingMode::None),
361                wrk,
362                RoundingMode::None,
363            );
364            f(&x, wrk, RoundingMode::None, cc)
365        };
366        let y0 = eval(&xi);
367        if !finite(&y0) {
368            if k == 0 {
369                return None;
370            }
371            break;
372        }
373        acc = acc.add(
374            &w.mul(&y0, wrk, RoundingMode::None),
375            wrk,
376            RoundingMode::None,
377        );
378        if k > 0 {
379            let y1 = eval(&xi.neg());
380            if finite(&y1) {
381                acc = acc.add(
382                    &w.mul(&y1, wrk, RoundingMode::None),
383                    wrk,
384                    RoundingMode::None,
385                );
386            }
387        }
388    }
389    let mut out = half
390        .mul(h, wrk, RoundingMode::None)
391        .mul(&acc, wrk, RoundingMode::None);
392    let _ = out.set_precision(p, rm);
393    Some(out)
394}
395
396/// Tanh–sinh (double-exponential) quadrature of `f` on `[a, b]`.
397///
398/// `x = tanh((π/2) sinh t)`, step `h = 2π / (p ln 2)` then up to
399/// [`TANH_SINH_LEVELS_MAX`] successive halvings until successive sums
400/// agree to working precision. `None` if the interval is not a finite `a < b`.
401pub fn tanh_sinh<F>(
402    mut f: F,
403    a: &ExactNum,
404    b: &ExactNum,
405    p: usize,
406    rm: RoundingMode,
407    cc: &mut Consts,
408) -> Option<ExactNum>
409where
410    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
411{
412    if !finite(a) || !finite(b) || a.cmp(b) != Some(-1) {
413        return None;
414    }
415    let wrk = work_p(p);
416    let two = ExactNum::from_u8(2, wrk);
417    let pi = cc.pi(wrk, RoundingMode::None);
418    let ln2 = cc.ln_2(wrk, RoundingMode::None);
419    let p_f = ExactNum::from_u32(p as u32, wrk);
420    let h0 = two.mul(&pi, wrk, RoundingMode::None).div(
421        &p_f.mul(&ln2, wrk, RoundingMode::None),
422        wrk,
423        RoundingMode::None,
424    );
425    let guard = tiny(p, 8);
426    let mut prev: Option<ExactNum> = None;
427    let mut best = None;
428    let two_wrk = ExactNum::from_u8(2, wrk);
429    let mut h = h0;
430    for level in 0..TANH_SINH_LEVELS_MAX {
431        if level > 0 {
432            h = h.div(&two_wrk, wrk, RoundingMode::None);
433        }
434        let s = tanh_sinh_sum(&mut f, a, b, &h, p, rm, cc)?;
435        if let Some(pr) = &prev {
436            let err = s.sub(pr, p, rm).abs();
437            if below(&err, &guard) {
438                return Some(s);
439            }
440        }
441        prev = Some(s.clone());
442        best = Some(s);
443    }
444    best
445}
446
447fn gauss_laguerre_nodes(
448    n: usize,
449    p: usize,
450    _rm: RoundingMode,
451    cc: &mut Consts,
452) -> Option<Vec<(ExactNum, ExactNum)>> {
453    if n == 0 || n > QUADRATURE_MAX_NODES {
454        return None;
455    }
456    let wrk = work_p(p);
457    let nu = n as u32;
458    let zero = ExactNum::new(wrk);
459    let hi = ExactNum::from_u32((4 * n + 4) as u32, wrk);
460    let brackets = isolate_positive(
461        |x, p, rm, _cc| x.laguerre(n, p, rm),
462        &zero,
463        &hi,
464        n,
465        wrk,
466        RoundingMode::None,
467        cc,
468    )?;
469    let n_f = ExactNum::from_u32(nu, wrk);
470    let np1 = ExactNum::from_u32((n + 1) as u32, wrk);
471    let np1sq = np1.mul(&np1, wrk, RoundingMode::None);
472    let mut nodes = Vec::with_capacity(n);
473    for (a, b) in brackets {
474        let x0 = bisect_zero(
475            |x, p, rm, _cc| x.laguerre(n, p, rm),
476            a,
477            b,
478            wrk,
479            RoundingMode::None,
480            cc,
481        )?;
482        let x = newton(
483            |x, p, rm, _cc| x.laguerre(n, p, rm),
484            |x, p, rm, _cc| {
485                if x.is_zero() {
486                    return ExactNum::nan(None);
487                }
488                let ln = x.laguerre(n, p, rm);
489                let lnm = if n == 0 { ExactNum::new(p) } else { x.laguerre(n - 1, p, rm) };
490                n_f.mul(&ln.sub(&lnm, p, rm), p, rm).div(x, p, rm)
491            },
492            x0,
493            wrk,
494            RoundingMode::None,
495            cc,
496        )?;
497        let ln1 = x.laguerre(n + 1, wrk, RoundingMode::None);
498        let w = x.div(
499            &np1sq.mul(
500                &ln1.mul(&ln1, wrk, RoundingMode::None),
501                wrk,
502                RoundingMode::None,
503            ),
504            wrk,
505            RoundingMode::None,
506        );
507        if !finite(&x) || !finite(&w) {
508            return None;
509        }
510        nodes.push((x, w));
511    }
512    Some(nodes)
513}
514
515/// Gauss–Laguerre quadrature: `∫₀^∞ f(x) e^{-x} dx` with `n` nodes.
516///
517/// Nodes are roots of `L_n`; weights `w_i = x_i / ((n+1)² [L_{n+1}(x_i)]²)`.
518/// Exact for `f` a polynomial of degree `≤ 2n−1`.
519pub fn gauss_laguerre<F>(
520    mut f: F,
521    n: usize,
522    p: usize,
523    rm: RoundingMode,
524    cc: &mut Consts,
525) -> Option<ExactNum>
526where
527    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
528{
529    let wrk = work_p(p);
530    let nodes = gauss_laguerre_nodes(n, wrk, RoundingMode::None, cc)?;
531    let mut acc = ExactNum::new(wrk);
532    for (xi, wi) in &nodes {
533        let y = f(xi, wrk, RoundingMode::None, cc);
534        if !finite(&y) {
535            return None;
536        }
537        acc = acc.add(
538            &wi.mul(&y, wrk, RoundingMode::None),
539            wrk,
540            RoundingMode::None,
541        );
542    }
543    let _ = acc.set_precision(p, rm);
544    Some(acc)
545}
546
547fn gauss_hermite_nodes(
548    n: usize,
549    p: usize,
550    _rm: RoundingMode,
551    cc: &mut Consts,
552) -> Option<Vec<(ExactNum, ExactNum)>> {
553    if n == 0 || n > QUADRATURE_MAX_NODES {
554        return None;
555    }
556    let wrk = work_p(p);
557    let nu = n as u32;
558    let two = ExactNum::from_u8(2, wrk);
559    let hi = ExactNum::from_u32((4 * n + 2) as u32, wrk).sqrt(wrk, RoundingMode::None);
560    let zero = ExactNum::new(wrk);
561    let want_pos = n / 2;
562    let brackets = isolate_positive(
563        |x, p, rm, _cc| x.hermite_h(n, p, rm),
564        &zero,
565        &hi,
566        want_pos,
567        wrk,
568        RoundingMode::None,
569        cc,
570    )?;
571    let n_f = ExactNum::from_u32(nu, wrk);
572    let nsq = n_f.mul(&n_f, wrk, RoundingMode::None);
573    let fact = factorial(n, wrk, RoundingMode::None);
574    let pi = cc.pi(wrk, RoundingMode::None);
575    let sqrt_pi = pi.sqrt(wrk, RoundingMode::None);
576    let two_pow = ExactNum::from_u8(1, wrk).ldexp((n as i32) - 1, wrk, RoundingMode::None);
577    let scale = two_pow
578        .mul(&fact, wrk, RoundingMode::None)
579        .mul(&sqrt_pi, wrk, RoundingMode::None);
580    let weight = |x: &ExactNum| {
581        let hm = if n == 0 {
582            ExactNum::from_u8(1, wrk)
583        } else {
584            x.hermite_h(n - 1, wrk, RoundingMode::None)
585        };
586        scale.div(
587            &nsq.mul(
588                &hm.mul(&hm, wrk, RoundingMode::None),
589                wrk,
590                RoundingMode::None,
591            ),
592            wrk,
593            RoundingMode::None,
594        )
595    };
596    let mut nodes = Vec::with_capacity(n);
597    if n % 2 == 1 {
598        let w0 = weight(&zero);
599        if !finite(&w0) {
600            return None;
601        }
602        nodes.push((zero.clone(), w0));
603    }
604    for (a, b) in brackets {
605        let x0 = bisect_zero(
606            |x, p, rm, _cc| x.hermite_h(n, p, rm),
607            a,
608            b,
609            wrk,
610            RoundingMode::None,
611            cc,
612        )?;
613        let x = newton(
614            |x, p, rm, _cc| x.hermite_h(n, p, rm),
615            |x, p, rm, _cc| {
616                if n == 0 {
617                    return ExactNum::new(p);
618                }
619                two.mul(&n_f, p, rm).mul(&x.hermite_h(n - 1, p, rm), p, rm)
620            },
621            x0,
622            wrk,
623            RoundingMode::None,
624            cc,
625        )?;
626        let w = weight(&x);
627        if !finite(&x) || !finite(&w) {
628            return None;
629        }
630        nodes.push((x.clone(), w.clone()));
631        nodes.push((x.neg(), w));
632    }
633    if nodes.len() != n {
634        return None;
635    }
636    Some(nodes)
637}
638
639/// Gauss–Hermite quadrature: `∫_{-∞}^{∞} f(x) e^{-x²} dx` with `n` nodes.
640///
641/// Nodes are roots of the physicist's `H_n`; weights
642/// `w_i = 2^{n-1} n! √π / (n² [H_{n-1}(x_i)]²)`.
643/// Exact for `f` a polynomial of degree `≤ 2n−1`.
644pub fn gauss_hermite<F>(
645    mut f: F,
646    n: usize,
647    p: usize,
648    rm: RoundingMode,
649    cc: &mut Consts,
650) -> Option<ExactNum>
651where
652    F: FnMut(&ExactNum, usize, RoundingMode, &mut Consts) -> ExactNum,
653{
654    let wrk = work_p(p);
655    let nodes = gauss_hermite_nodes(n, wrk, RoundingMode::None, cc)?;
656    let mut acc = ExactNum::new(wrk);
657    for (xi, wi) in &nodes {
658        let y = f(xi, wrk, RoundingMode::None, cc);
659        if !finite(&y) {
660            return None;
661        }
662        acc = acc.add(
663            &wi.mul(&y, wrk, RoundingMode::None),
664            wrk,
665            RoundingMode::None,
666        );
667    }
668    let _ = acc.set_precision(p, rm);
669    Some(acc)
670}
671
672#[cfg(test)]
673mod tests {
674    use super::*;
675    use crate::Consts;
676
677    fn gold_p() -> (usize, RoundingMode) {
678        (256, RoundingMode::ToEven)
679    }
680
681    #[test]
682    fn quadrature_gl_tanh_laguerre_hermite() {
683        let (p, rm) = gold_p();
684        let mut cc = Consts::new().expect("consts");
685        let a = ExactNum::from_i64(-1, p);
686        let b = ExactNum::from_u8(1, p);
687        let two = ExactNum::from_u8(2, p);
688        let three = ExactNum::from_u8(3, p);
689
690        let gl = gauss_legendre(|x, p, rm, _cc| x.mul(x, p, rm), &a, &b, 10, p, rm, &mut cc)
691            .expect("gl x^2");
692        let two_thirds = two.div(&three, p, rm);
693        assert_eq!(gl.cmp(&two_thirds), Some(0));
694
695        const GL_EXACT_DEG: u32 = 38;
696        const GL_EXACT_NODES: usize = 20;
697        let gl38 = gauss_legendre(
698            |x, p, rm, _cc| {
699                let mut y = ExactNum::from_u8(1, p);
700                for _ in 0..GL_EXACT_DEG {
701                    y = y.mul(x, p, rm);
702                }
703                y
704            },
705            &a,
706            &b,
707            GL_EXACT_NODES,
708            p,
709            rm,
710            &mut cc,
711        )
712        .expect("gl x^38");
713        let thirty_nine = ExactNum::from_u32(GL_EXACT_DEG + 1, p);
714        let two_over = two.div(&thirty_nine, p, rm);
715        assert_eq!(gl38.cmp(&two_over), Some(0));
716
717        let ts = tanh_sinh(
718            |x, p, rm, _cc| {
719                let one = ExactNum::from_u8(1, p);
720                one.sub(&x.mul(x, p, rm), p, rm)
721                    .sqrt(p, rm)
722                    .reciprocal(p, rm)
723            },
724            &a,
725            &b,
726            p,
727            rm,
728            &mut cc,
729        )
730        .expect("tanh-sinh arcsine");
731        let pi = cc.pi(p, rm);
732        assert_eq!(ts.cmp(&pi), Some(0));
733
734        let lag = gauss_laguerre(|x, p, rm, _cc| x.mul(x, p, rm), 10, p, rm, &mut cc)
735            .expect("laguerre x^2");
736        assert_eq!(lag.cmp(&two), Some(0));
737
738        let gh = gauss_hermite(|_x, p, _rm, _cc| ExactNum::from_u8(1, p), 1, p, rm, &mut cc)
739            .expect("hermite 1");
740        let sqrt_pi = pi.sqrt(p, rm);
741        assert_eq!(gh.cmp(&sqrt_pi), Some(0));
742
743        assert!(gauss_legendre(|x, _p, _rm, _cc| x.clone(), &b, &a, 4, p, rm, &mut cc).is_none());
744        assert!(gauss_legendre(|x, _p, _rm, _cc| x.clone(), &a, &b, 0, p, rm, &mut cc).is_none());
745    }
746}