1use crate::{
3 Arg, Native, dd::Double, exp::{exp, exp_narrow, exp_plus}, float::{horner, sealed::Core}, ln::{ln, ln_double, ln_narrow, ln_split, log1p}, slice::written
4};
5use std::{f64::consts::PI, mem::MaybeUninit};
6
7macro_rules! in_f64 {
9 ($($name:ident($($x:ident),+) = $core:ident;)+) => {$(
10 #[inline(always)]
11 pub(crate) fn $name<R: Native, const FUSED: bool>($($x: R),+) -> R { R::narrow($core::<FUSED>($($x.wide()),+)) }
12 )+};
13}
14
15in_f64! {
16 digamma(x) = digamma64;
17 polygamma(n, x) = polygamma64;
18 beta(a, b) = beta64;
19 binom_dn(n, k) = binom_dn64;
20 binom_dk(n, k) = binom_dk64;
21 gammaincinv(a, p) = gammaincinv64;
22 betainc(a, b, x) = betainc64;
23 betaincinv(a, b, p) = betaincinv64;
24}
25
26#[inline(always)]
28pub(crate) fn gamma<R: Native, const FUSED: bool>(x: R) -> R { R::narrow(if R::WIDE { gamma64::<FUSED>(x.wide()) } else { gamma_plain::<FUSED>(x.wide()) }) }
29
30#[inline(always)]
32pub(crate) fn factorial<R: Native, const FUSED: bool>(x: R) -> R { gamma::<R, FUSED>(x + R::one()) }
33
34#[inline(always)]
36pub(crate) fn lgamma<R: Native, const FUSED: bool>(x: R) -> R {
37 R::narrow(if R::WIDE { lgamma64::<FUSED>(x.wide()) } else { lgamma_stirling::<FUSED>(x.wide()) })
38}
39
40const BINOM_STEPS: (f64, f64) = (600.0, 80.0);
42
43const FALLING_STEPS: f64 = 64.0;
45
46#[inline(always)]
48fn ratio_step<R: Native>(a: R, m: R, d: R) -> R { let p = a * m; if p.is_finite() { p / d } else { a / d * m } }
49
50fn falling(n: f64, k: f64) -> (f64, f64) {
52 let (mut c, mut slope, mut i) = (1.0, 0.0, 0.0);
53 while i < k {
54 let next = i + 1.0;
55 slope = ratio_step(slope, n - i, next) + c / next;
56 c = ratio_step(c, n - i, next);
57 i = next;
58 }
59 (c, slope)
60}
61
62#[inline]
64pub(crate) fn binom<R: Native, const FUSED: bool>(n: R, k: R) -> R {
65 let whole = |v: R| v.trunc() == v;
66 if whole(n) & whole(k) & (k >= R::zero()) {
67 if (n >= R::zero()) & (k > n) { return R::zero(); }
68 if k.is_finite() {
69 let (top, negative) = if n >= R::zero() { (n, false) } else { (k - n - R::one(), (k * R::narrow(0.5)).fract() != R::zero()) };
70 let steps = if k < top - k { k } else { top - k };
71 let c = if steps > R::narrow(if R::WIDE { BINOM_STEPS.0 } else { BINOM_STEPS.1 }) { R::infinity() } else {
72 let (mut c, mut i) = (R::one(), R::zero());
73 while i < steps {
74 c = ratio_step(c, top - i, i + R::one());
75 i = i + R::one();
76 }
77 c
78 };
79 return if negative { -c } else { c };
80 }
81 }
82 if whole(n) & whole(k) & (k < R::zero()) { return if (n < R::zero()) & (k <= n) { binom::<R, FUSED>(n, n - k) } else { R::zero() }; }
84 if whole(k) & (k >= R::zero()) & (k <= R::narrow(FALLING_STEPS)) { return R::narrow(falling(n.wide(), k.wide()).0); }
85 R::narrow(binom_real::<FUSED>(n.wide(), k.wide()))
86}
87
88fn gamma_args(n: f64, k: f64) -> (Double<f64>, Double<f64>, Double<f64>) {
90 (Double::sum(n, 1.0), Double::sum(k, 1.0), Double::sum(n, -k).plus(Double::new(1.0, 0.0)).normalize())
91}
92
93fn binom_real<const FUSED: bool>(n: f64, k: f64) -> f64 {
95 if !(n.is_finite() && k.is_finite()) { return if n.is_finite() && k.is_infinite() { 0.0 } else { f64::NAN }; }
96 let (a, b, c) = gamma_args(n, k);
97 gamma_ratio::<FUSED>([(a, true), (b, false), (c, false)])
98}
99
100fn beta64<const FUSED: bool>(a: f64, b: f64) -> f64 {
102 if !(a.is_finite() && b.is_finite()) {
103 let vanishes = |x: f64, y: f64| x == f64::INFINITY && y.is_finite() && y > 0.0;
104 return if vanishes(a, b) || vanishes(b, a) { 0.0 } else { f64::NAN };
105 }
106 gamma_ratio::<FUSED>([(Double::new(a, 0.0), true), (Double::new(b, 0.0), true), (Double::sum(a, b), false)])
107}
108
109fn gamma_ratio<const FUSED: bool>(mut terms: [(Double<f64>, bool); 3]) -> f64 {
111 let pole = |y: Double<f64>| y.hi <= 0.0 && y.hi.fract() == 0.0 && y.lo.fract() == 0.0;
112 if terms.iter().any(|&(y, up)| up && pole(y)) { return f64::NAN; }
113 if terms.iter().any(|&(y, up)| !up && pole(y)) { return 0.0; }
114 let (mut sign, mut log) = (1.0, Double::new(0.0, 0.0));
115 for t in &mut terms {
116 if t.0.hi < 0.5 {
117 let (z, l, s) = reflection::<FUSED>(t.0);
118 sign *= s;
119 log = log.plus(if t.1 { l } else { -l });
120 *t = (z, !t.1);
121 }
122 }
123 let gap = |&(i, j): &(usize, usize)| (terms[i].0.hi - terms[j].0.hi).abs();
124 let pair = (0..3).flat_map(|i| (0..3).map(move |j| (i, j))).filter(|&(i, j)| terms[i].1 && !terms[j].1 && terms[i].0.hi >= 10.0 && terms[j].0.hi >= 10.0)
125 .min_by(|p, q| gap(p).total_cmp(&gap(q)));
126 if let Some((i, j)) = pair { log = log.plus(lgamma_gap::<FUSED>(terms[i].0, terms[j].0)); }
127 for (i, &(z, up)) in terms.iter().enumerate() {
128 if pair.is_some_and(|(p, q)| i == p || i == q) { continue; }
129 let l = lgamma_dd::<FUSED>(z);
130 log = log.plus(if up { l } else { -l });
131 }
132 sign * exp_plus::<f64, FUSED>(log)
133}
134
135const G: f64 = 6.024680040776729583740234375;
137const G_HALF: f64 = 5.524680040776729583740234375;
138const EXP_NEG_G: f64 = 0.00241832520904651;
140const LN2_512: (f64, f64) = (354.891356446692, 1.1873519686893054e-14);
142const TWO_M512: f64 = 7.458340731200207e-155;
143const PI_LO: f64 = 1.2246467991473532e-16;
145pub(crate) const HALF_LN_2PI: f64 = 0.9189385332046728;
147pub(crate) const STIRLING: [f64; 10] = [
148 -1.3924322169059011, 0.17964437236883057, -0.029550653594771242, 0.00641025641025641, -0.0019175269175269176, 0.0008417508417508417, -0.0005952380952380953,
149 0.0007936507936507937, -0.002777777777777778, 0.08333333333333333,
150];
151
152const NUM: [f64; 13] = [
154 23531376880.410759688572007674451636754734846804940, 42919803642.649098768957899047001988850926355848959,
155 35711959237.355668049440185451547166705960488635843, 17921034426.037209699919755754458931112671403265390,
156 6039542586.3520280050642916443072979210699388420708, 1439720407.3117216736632230727949123939715485786772,
157 248874557.86205415651146038641322942321632125127801, 31426415.585400194380614231628318205362874684987640,
158 2876370.6289353724412254090516208496135991145378768, 186056.26539522349504029498971604569928220784236328,
159 8071.6720023658162106380029022722506138218516325024, 210.82427775157934587250973392071336271166969580291,
160 2.5066282746310002701649081771338373386264310793408,
161];
162const DEN: [f64; 13] = [0.0, 39916800.0, 120543840.0, 150917976.0, 105258076.0, 45995730.0, 13339535.0, 2637558.0, 357423.0, 32670.0, 1925.0, 66.0, 1.0];
163#[inline(always)]
165fn lanczos<const FUSED: bool>(y: Double<f64>) -> (Double<f64>, Double<f64>) {
166 let v = 1.0 / y.hi;
167 (Double::horner::<FUSED>(v, NUM).div::<FUSED>(Double::horner::<FUSED>(v, DEN)), exponent::<FUSED>(y, true))
168}
169
170#[inline(always)]
172fn exponent<const FUSED: bool>(y: Double<f64>, fine: bool) -> Double<f64> {
173 let l = ln_dd::<FUSED>(Double::sum(y.hi, G_HALF).plus(Double::new(y.lo, 0.0)), fine);
174 let m = l.plus(Double::new(-1.0, 0.0));
175 let e = m.mul_float::<FUSED>(y.hi - 0.5);
176 Double::new(e.hi, e.lo + m.hi * y.lo)
177}
178
179#[inline(always)]
181fn ln_dd<const FUSED: bool>(u: Double<f64>, fine: bool) -> Double<f64> {
182 let l = if fine { ln_double::<FUSED>(u.hi) } else { ln_split::<FUSED>(u.hi) };
183 Double::new(l.hi, l.lo + u.lo / u.hi)
184}
185
186fn reflection<const FUSED: bool>(x: Double<f64>) -> (Double<f64>, Double<f64>, f64) {
188 let n = x.hi.round();
189 let s = sinpi::<FUSED>((x.hi - n) + x.lo);
190 let s = if (n * 0.5).fract() != 0.0 { -s } else { s };
191 (Double::sum(1.0, -x.hi).plus(Double::new(-x.lo, 0.0)), ln_dd::<FUSED>(Double::new(PI, PI_LO).div::<FUSED>(Double::new(s.abs(), 0.0)), true), s.signum())
192}
193
194fn lgamma_dd<const FUSED: bool>(y: Double<f64>) -> Double<f64> {
196 if y.hi < 0.5 { let (z, l, _) = reflection::<FUSED>(y); return l.plus(-lgamma_dd::<FUSED>(z)); }
197 let (s, e) = lanczos::<FUSED>(y);
198 e.plus(Double::new(ln::<f64, FUSED>(s.hi * EXP_NEG_G), s.lo / s.hi))
199}
200
201fn lgamma_gap<const FUSED: bool>(z: Double<f64>, w: Double<f64>) -> Double<f64> {
203 let times = |x: Double<f64>, y: Double<f64>| x.mul_float::<FUSED>(y.hi).plus(Double::new(x.hi * y.lo, 0.0));
204 let tail = |y: Double<f64>| { let v = 1.0 / y.hi; v * horner::<f64, FUSED, 10>(v * v, STIRLING) };
205 let (d, r) = ln_ratio::<FUSED>(z, w);
206 times(r, z.plus(Double::new(-0.5, 0.0))).plus(times(ln_dd::<FUSED>(w, true), d)).plus(-d).plus(Double::new(tail(z) - tail(w), 0.0))
207}
208
209fn psi_gap<const FUSED: bool>(z: Double<f64>, w: Double<f64>) -> f64 {
211 let tail = |y: Double<f64>| { let v = 1.0 / (y.hi * y.hi); v * horner::<f64, FUSED, 8>(v, PSI_SERIES) };
212 let (d, r) = ln_ratio::<FUSED>(z, w);
213 r.hi + (r.lo + 0.5 * d.hi / (z.hi * w.hi) - (tail(z) - tail(w)))
214}
215
216fn ln_ratio<const FUSED: bool>(z: Double<f64>, w: Double<f64>) -> (Double<f64>, Double<f64>) {
218 let d = z.plus(-w).normalize();
219 let x = d.div::<FUSED>(w);
220 (d, ln_dd::<FUSED>(Double::sum(1.0, x.hi).plus(Double::new(x.lo, 0.0)), true))
221}
222
223const SIN_PI: [f64; 9] = [
225 7.952054001475513e-07, -2.1915353447830217e-05, 0.00046630280576761255, -0.0073704309457143504, 0.08214588661112823, -0.5992645293207921,
226 2.5501640398773455, -5.16771278004997, 3.141592653589793,
227];
228const COS_PI: [f64; 10] = [
229 -1.3878952462213771e-07, 4.303069587032947e-06, -0.0001046381049248457, 0.0019295743094039231, -0.02580689139001406, 0.2353306303588932,
230 -1.3352627688545895, 4.0587121264167685, -4.934802200544679, 1.0,
231];
232
233#[inline(always)]
235fn sinpi<const FUSED: bool>(x: f64) -> f64 {
236 let n = x.round();
237 let r = x - n;
238 let (a, b) = (r.abs(), 0.5 - r.abs());
239 let s = if a <= 0.25 { a * horner::<f64, FUSED, 9>(a * a, SIN_PI) } else { horner::<f64, FUSED, 10>(b * b, COS_PI) };
240 let half = n * 0.5;
241 let s = s.copysign(r);
242 if half != half.floor() { -s } else { s }
243}
244
245const SIN_PI_PLAIN: [f64; 6] = [-0.007022080551569066, 0.08204642580715658, -0.5992513181742443, 2.550163239211499, -5.167712762079367, 3.141592653523149];
247
248#[inline(always)]
250fn sinpi_plain<const FUSED: bool>(x: f64) -> f64 {
251 let n = x.round_ties_even();
252 let r = x - n;
253 let s = r * horner::<f64, FUSED, 6>(r * r, SIN_PI_PLAIN);
254 let half = n * 0.5;
255 if half != half.floor() { -s } else { s }
256}
257
258#[inline(always)]
260fn reflected(x: f64) -> (bool, Double<f64>) { let reflect = x < 0.5; (reflect, if reflect { Double::sum(1.0, -x) } else { Double::new(x, 0.0) }) }
261
262#[inline(always)]
263fn gamma64<const FUSED: bool>(x: f64) -> f64 {
264 let (reflect, y) = reflected(x);
265 let (s, e) = lanczos::<FUSED>(y);
266 let d = Double::sum(e.hi, -G);
268 let (hi, lo) = (d.hi, d.lo + e.lo);
269 let up = Double::sum(-hi, LN2_512.0);
270 let p = if reflect { exp_plus::<f64, FUSED>(Double::new(up.hi, up.lo - lo + LN2_512.1)) } else { exp_plus::<f64, FUSED>(Double::new(hi, lo)) };
271 let f = Double::new(PI, PI_LO).div::<FUSED>(s.mul_float::<FUSED>(sinpi::<FUSED>(x)));
273 let g = if reflect { (f.hi * TWO_M512).mla::<FUSED>(p, f.lo * TWO_M512 * p) } else { s.hi.mla::<FUSED>(p, s.lo * p) };
274 if x == 0.0 { 1.0 / x } else if x < 0.0 && x == x.floor() { f64::NAN } else if x > 171.7 { f64::INFINITY } else { g }
275}
276
277#[inline(always)]
278fn lgamma64<const FUSED: bool>(x: f64) -> f64 {
279 let (reflect, y) = reflected(x);
280 let s = sum_plain::<FUSED>(y.hi) * EXP_NEG_G;
282 let e = exponent::<FUSED>(y, false);
283 let tiny = x.abs() < 1e-17;
285 let l = ln_split::<FUSED>(if tiny { x.abs() } else if reflect { PI / (sinpi::<FUSED>(x).abs() * s) } else { s });
286 let r = l.plus(if reflect { -e } else { e });
287 if x.is_infinite() || (x <= 0.0 && x == x.floor()) { f64::INFINITY } else if tiny { -l.hi } else { r.hi + r.lo }
288}
289
290#[inline(always)]
292fn sum_plain<const FUSED: bool>(y: f64) -> f64 { let v = 1.0 / y; horner::<f64, FUSED, 13>(v, NUM) / horner::<f64, FUSED, 13>(v, DEN) }
293
294#[inline(always)]
296fn gamma_plain<const FUSED: bool>(x: f64) -> f64 {
297 let reflect = x < 0.5;
298 let y = if reflect { 1.0 - x } else { x };
299 let s = sum_plain::<FUSED>(y);
300 let p = exp_narrow::<FUSED>((y - 0.5) * (ln::<f64, FUSED>(y + G_HALF) - 1.0) - G);
301 let g = if reflect { PI / (sinpi_plain::<FUSED>(x) * s * p) } else { s * p };
302 if x == 0.0 { 1.0 / x } else if x < 0.0 && x == x.floor() { f64::NAN } else { g }
303}
304
305#[inline(always)]
307fn shifted(y: f64) -> (f64, f64) {
308 let p = (y * (y + 1.0)) * ((y + 2.0) * (y + 3.0)) * ((y + 4.0) * (y + 5.0)) * ((y + 6.0) * (y + 7.0));
309 if y < 8.0 { (y + 8.0, p) } else { (y, 1.0) }
310}
311
312#[inline(always)]
314fn stirling_tail<const FUSED: bool>(w: f64) -> f64 { w.mla::<FUSED>(horner::<f64, FUSED, 3>(w * w, [STIRLING[7], STIRLING[8], STIRLING[9]]), HALF_LN_2PI) }
315
316#[inline(always)]
318fn lgamma_stirling<const FUSED: bool>(x: f64) -> f64 {
319 let reflect = x < 0.5;
320 let (z, p) = shifted(if reflect { 1.0 - x } else { x });
321 let s = (z - 0.5).mla::<FUSED>(ln_narrow::<FUSED>(z), stirling_tail::<FUSED>(1.0 / z) - z);
322 let (num, den) = if reflect { (PI * p, sinpi_plain::<FUSED>(x).abs()) } else { (1.0, p) };
323 let l = ln_narrow::<FUSED>(num / den);
324 let r = if reflect { l - s } else { s + l };
325 if x.is_infinite() || (x <= 0.0 && x == x.floor()) { f64::INFINITY } else { r }
326}
327
328const PSI_SERIES: [f64; 8] = [-3617.0 / 8160.0, 1.0 / 12.0, -691.0 / 32760.0, 1.0 / 132.0, -1.0 / 240.0, 1.0 / 252.0, -1.0 / 120.0, 1.0 / 12.0];
330const PSI_REFLECT: f64 = -20.0;
332
333fn psi_series<const FUSED: bool>(z: Double<f64>) -> Double<f64> {
335 let w = 1.0 / (z.hi * z.hi);
336 let tail = w * horner::<f64, FUSED, 8>(w, PSI_SERIES);
337 ln_dd::<FUSED>(z, true).plus(-Double::new(0.5, 0.0).div::<FUSED>(z)).plus(Double::new(-tail, 0.0))
338}
339
340fn digamma64<const FUSED: bool>(x: f64) -> f64 {
342 if x.is_nan() || x == f64::NEG_INFINITY || (x < 0.0 && x == x.floor()) { return f64::NAN; }
343 if x.abs() < 1e-18 { return -1.0 / x; }
345 if x == f64::INFINITY { return x; }
346 if x < PSI_REFLECT {
347 let r = psi_series::<FUSED>(reflected(x).1);
349 return (r.hi - PI * sinpi::<FUSED>(x + 0.5) / sinpi::<FUSED>(x)) + r.lo;
350 }
351 let n = (10.0 - x).ceil().max(0.0);
352 let mut lifted = Double::new(0.0, 0.0);
353 for k in 0..n as u32 { lifted = lifted.plus(Double::new(1.0, 0.0).div::<FUSED>(Double::sum(x, k as f64))); }
354 let r = psi_series::<FUSED>(Double::sum(x, n)).plus(-lifted);
355 r.hi + r.lo
356}
357
358const POLYGAMMA_SERIES: [f64; 8] =
360 [1.0 / 12.0, -1.0 / 720.0, 1.0 / 30240.0, -1.0 / 1209600.0, 1.0 / 47900160.0, -691.0 / 1307674368000.0, 1.0 / 74724249600.0, -3617.0 / 10670622842880000.0];
361
362fn polygamma64<const FUSED: bool>(n: f64, x: f64) -> f64 {
364 if n == 0.0 { return digamma64::<FUSED>(x); }
365 if !(n > 0.0 && n <= 170.0 && n == n.trunc() && x > 0.0) { return f64::NAN; }
366 let odd = n % 2.0 == 1.0;
367 if x == f64::INFINITY { return if odd { 0.0 } else { -0.0 }; }
368 let inverse_power = |y: Double<f64>, e: f64| {
370 let mut p = Double::new(y.hi, 0.0);
371 for _ in 1..e as u32 { p = p.mul_float::<FUSED>(y.hi); }
372 Double::new(1.0, 0.0).div::<FUSED>(p.plus(p.mul_float::<FUSED>(e * y.lo / y.hi)))
373 };
374 let m = (15.0 + 2.0 * n - x).ceil().max(0.0);
375 let mut lifted = Double::new(0.0, 0.0);
376 for k in (0..m as u32).rev() { lifted = lifted.plus(inverse_power(Double::sum(x, k as f64), n + 1.0)); }
377 let z = Double::sum(x, m);
378 let w = 1.0 / (z.hi * z.hi);
379 let (mut rising, mut tail) = (1.0, [0.0; 8]);
380 for (k, c) in POLYGAMMA_SERIES.into_iter().enumerate() {
381 let j = 2.0 * k as f64;
382 rising *= (n + j) * (n + j + 1.0);
383 tail[7 - k] = c * rising;
384 }
385 let series = Double::sum(1.0, n / (2.0 * z.hi)).plus(Double::new(w * horner::<f64, FUSED, 8>(w, tail), 0.0));
386 let leading = inverse_power(z, n);
387 let total = lifted.mul_float::<FUSED>(n).plus(leading.mul_float::<FUSED>(series.hi).plus(Double::new(leading.hi * series.lo, 0.0)));
388 let r = total.mul_float::<FUSED>((1..n as u32).fold(1.0, |f, i| f * i as f64));
389 let r = if r.hi.is_finite() { r.hi + r.lo } else { r.hi };
391 if odd { r } else { -r }
392}
393
394fn binom_slopes<const FUSED: bool>(n: f64, k: f64) -> (f64, f64) {
396 let whole = |v: f64| v == v.trunc();
397 let psi = digamma64::<FUSED>;
398 if whole(k) && k >= 0.0 && k <= FALLING_STEPS {
399 let (c, dn) = falling(n, k);
400 return (dn, if whole(n) && (0.0..k).contains(&n) { -dn } else { c * (psi(n - k + 1.0) - psi(k + 1.0)) });
402 }
403 let c = binom::<f64, FUSED>(n, k);
404 if whole(k) && k >= 0.0 && whole(n) && n < k {
405 if n >= 0.0 {
406 let sign = if (k - 1.0 - n) % 2.0 == 0.0 { 1.0 } else { -1.0 };
408 let slope = sign / (k * binom::<f64, FUSED>(k - 1.0, n));
409 return (slope, -slope);
410 }
411 return (c * (psi(-n) - psi(k - n)), c * (psi(n - k + 1.0) - psi(k + 1.0)));
413 }
414 let (a, b, rest) = gamma_args(n, k);
415 let gap = |z: Double<f64>, w: Double<f64>| if z.hi >= 10.0 && w.hi >= 10.0 { psi_gap::<FUSED>(z, w) } else { psi(z.hi) - psi(w.hi) };
416 (c * gap(a, rest), c * gap(rest, b))
417}
418fn binom_dn64<const FUSED: bool>(n: f64, k: f64) -> f64 { binom_slopes::<FUSED>(n, k).0 }
419fn binom_dk64<const FUSED: bool>(n: f64, k: f64) -> f64 { binom_slopes::<FUSED>(n, k).1 }
420
421struct Erf { erx: f64, pp: [f64; 5], qq: [f64; 6], pa: [f64; 7], qa: [f64; 7], ra: [f64; 8], sa: [f64; 9], rb: [f64; 8], sb: [f64; 9] }
424
425const ERF64: Erf = Erf { erx: 8.45062911510467529297e-01,
426 pp: [-2.37630166566501626084e-05, -5.77027029648944159157e-03, -2.84817495755985104766e-02, -3.25042107247001499370e-01, 1.28379167095512558561e-01],
427 qq: [-3.96022827877536812320e-06, 1.32494738004321644526e-04, 5.08130628187576562776e-03, 6.50222499887672944485e-02, 3.97917223959155352819e-01, 1.0],
428 pa: [
429 -2.16637559486879084300e-03, 3.54783043256182359371e-02, -1.10894694282396677476e-01, 3.18346619901161753674e-01, -3.72207876035701323847e-01,
430 4.14856118683748331666e-01, -2.36211856075265944077e-03,
431 ], qa: [
432 1.19844998467991074170e-02, 1.36370839120290507362e-02, 1.26171219808761642112e-01, 7.18286544141962662868e-02, 5.40397917702171048937e-01,
433 1.06420880400844228286e-01, 1.0,
434 ], ra: [
435 -9.81432934416914548592e+00, -8.12874355063065934246e+01, -1.84605092906711035994e+02, -1.62396669462573470355e+02, -6.23753324503260060396e+01,
436 -1.05586262253232909814e+01, -6.93858572707181764372e-01, -9.86494403484714822705e-03,
437 ], sa: [
438 -6.04244152148580987438e-02, 6.57024977031928170135e+00, 1.08635005541779435134e+02, 4.29008140027567833386e+02, 6.45387271733267880336e+02,
439 4.34565877475229228821e+02, 1.37657754143519042600e+02, 1.96512716674392571292e+01, 1.0,
440 ], rb: [
441 0.0, -4.83519191608651397019e+02, -1.02509513161107724954e+03, -6.37566443368389627722e+02, -1.60636384855821916062e+02, -1.77579549177547519889e+01,
442 -7.99283237680523006574e-01, -9.86494292470009928597e-03,
443 ], sb: [
444 0.0, -2.24409524465858183362e+01, 4.74528541206955367215e+02, 2.55305040643316442583e+03, 3.19985821950859553908e+03, 1.53672958608443695994e+03,
445 3.25792512996573918826e+02, 3.03380607434824582924e+01, 1.0,
446 ] };
447
448const ERF32: Erf = Erf { erx: 0.8450629115104675,
449 pp: [-2.3763017452438362e-05, -0.005770270247012377, -0.028481749817728996, -0.32504209876060486, 0.12837916612625122],
450 qq: [-3.9602282413397916e-06, 0.0001324947370449081, 0.0050813062116503716, 0.06502225250005722, 0.3979172110557556, 1.0], pa: [
451 -0.002166375517845154, 0.03547830507159233, -0.11089469492435455, 0.31834661960601807, -0.3722078800201416, 0.41485610604286194, -0.0023621185682713985,
452 ], qa: [0.011984500102698803, 0.01363708358258009, 0.12617121636867523, 0.07182865589857101, 0.5403979420661926, 0.10642088204622269, 1.0], ra: [
453 -9.814329147338867, -81.28743743896484, -184.60508728027344, -162.39666748046875, -62.37533187866211, -10.558626174926758, -0.6938585638999939,
454 -0.009864944033324718,
455 ], sa: [
456 -0.06042441353201866, 6.570249557495117, 108.63500213623047, 429.0081481933594, 645.3872680664062, 434.5658874511719, 137.6577606201172,
457 19.65127182006836, 1.0,
458 ],
459 rb: [0.0, -483.5191955566406, -1025.0950927734375, -637.5664672851562, -160.63638305664062, -17.75795555114746, -0.7992832660675049, -0.009864943102002144],
460 sb: [0.0, -22.44095230102539, 474.5285339355469, 2553.05029296875, 3199.858154296875, 1536.7296142578125, 325.7925109863281, 30.33806037902832, 1.0] };
461
462#[inline(always)]
463fn erf_table<R: Native>() -> &'static Erf { if R::WIDE { &ERF64 } else { &ERF32 } }
464
465#[inline(always)]
467fn horner_either<R: Native, const FUSED: bool, const N: usize>(x: R, first: bool, a: [f64; N], b: [f64; N]) -> R {
468 let pick = |i: usize| if first { R::narrow(a[i]) } else { R::narrow(b[i]) };
469 let mut y = pick(0);
470 for i in 1..N { y = y.mla::<FUSED>(x, pick(i)); }
471 y
472}
473
474#[inline(always)]
476fn erf_small<R: Native, const FUSED: bool>(x: R) -> R {
477 let t = erf_table::<R>();
478 let z = x * x;
479 horner::<R, FUSED, 5>(z, t.pp) / horner::<R, FUSED, 6>(z, t.qq)
480}
481
482#[inline(always)]
484fn erfc_tail<R: Native, const FUSED: bool>(a: R) -> R {
485 let t = erf_table::<R>();
486 let one = R::one();
487 either(
488 a < R::narrow(1.25),
489 #[inline(always)]
490 || {
491 let s = a - one;
492 (one - R::narrow(t.erx)) - horner::<R, FUSED, 7>(s, t.pa) / horner::<R, FUSED, 7>(s, t.qa)
493 },
494 #[inline(always)]
495 || {
496 let v = one / (a * a);
497 let near = a < R::narrow(1.0 / 0.35);
498 let rs = horner_either::<R, FUSED, 8>(v, near, t.ra, t.rb) / horner_either::<R, FUSED, 9>(v, near, t.sa, t.sb);
499 let z = a.upper();
501 let h = Double::sum(-z * z, R::narrow(-0.5625));
502 let u = Double::sum(h.hi, (z - a) * (z + a) + rs);
503 exp_plus::<R, FUSED>(Double::new(u.hi, u.lo + h.lo)) / a
504 },
505 )
506}
507
508#[inline(always)]
510fn branches<R: Native>() -> bool { R::WIDE && !cfg!(target_arch = "x86_64") }
511
512#[inline(always)]
514fn either<R: Native>(use_first: bool, first: impl Fn() -> R, second: impl Fn() -> R) -> R {
515 if branches::<R>() { return if use_first { first() } else { second() }; }
516 let (a, b) = (first(), second());
517 if use_first { a } else { b }
518}
519
520const ERF_A: [f64; 6] = [-0.0005991049692966044, 0.004993395879864693, -0.026766734197735786, 0.11281802505254745, -0.37612485885620117, 0.12837915122509003];
522const ERF_B: [f64; 7] =
523 [1.729489304125309e-05, -0.00038320868043228984, 0.003883934346958995, -0.024254560470581055, 0.1067778468132019, 0.6348466873168945, 0.12871751189231873];
524const ERF_A64: [f64; 13] = [
526 4.649419429428393e-11, -1.042430407003087e-09, 1.435492841519301e-08, -1.629337626683206e-07, 1.6454188634626338e-06, -1.492505258803544e-05,
527 0.0001205530210596214, -0.0008548325957658537, 0.0052239776019798794, -0.026866170642096086, 0.11283791670935252, -0.37612638903183276, 0.12837916709551256,
528];
529const ERF_B64: [f64; 14] = [
530 1.1301887528580086e-10, -5.139418309129716e-09, 1.0625456517811032e-07, -1.334618914272856e-06, 1.1433194544113143e-05, -7.070387057856521e-05,
531 0.0003228431634442731, -0.0010706392328036834, 0.0022821868790661145, -0.0007443657653741413, -0.01877248218879148, 0.10260955401906749, 0.63666577565146,
532 0.12837314142093484,
533];
534
535#[inline(always)]
537fn erf_near<R: Native, const FUSED: bool, const A: usize>(x: R, a: [f64; A]) -> R { horner::<R, FUSED, A>(x * x, a).mla::<FUSED>(x, x) }
538
539#[inline(always)]
541fn erf_poly<R: Native, const FUSED: bool, const A: usize, const B: usize>(x: R, a: [f64; A], b: [f64; B], split: f64, top: f64) -> R {
542 let m = x.abs();
543 let small = erf_near::<R, FUSED, A>(x, a);
544 let large = (R::one() - exp::<R, FUSED>(-horner::<R, FUSED, B>(m, b).mla::<FUSED>(m, m))).copysign(x);
545 if x.is_nan() { x } else if m < R::narrow(split) { small } else if m < R::narrow(top) { large } else { R::one().copysign(x) }
546}
547
548#[inline(always)]
550pub(crate) fn erf<R: Native, const FUSED: bool>(x: R) -> R {
551 if R::WIDE { erf_poly::<R, FUSED, 13, 14>(x, ERF_A64, ERF_B64, 1.25, 6.0) } else { erf_poly::<R, FUSED, 6, 7>(x, ERF_A, ERF_B, 0.875, 4.0) }
552}
553
554#[inline(always)]
556pub(crate) fn erfc<R: Native, const FUSED: bool>(x: R) -> R {
557 erfc_ends(x, either(x.abs() < R::narrow(ERFC_SPLIT), #[inline(always)] || erfc_near::<R, FUSED>(x), #[inline(always)] || erfc_far::<R, FUSED>(x)))
558}
559
560const ERFC_SPLIT: f64 = 0.84375;
562
563#[inline(always)]
565fn erfc_near<R: Native, const FUSED: bool>(x: R) -> R {
566 let (one, half) = (R::one(), R::narrow(0.5));
567 let y = erf_small::<R, FUSED>(x);
568 if x < R::narrow(0.25) { one - (x + x * y) } else { half - (x - half + x * y) }
569}
570
571#[inline(always)]
573fn erfc_far<R: Native, const FUSED: bool>(x: R) -> R { let t = erfc_tail::<R, FUSED>(x.abs()); if x < R::zero() { R::narrow(2.0) - t } else { t } }
574
575#[inline(always)]
577fn erfc_ends<R: Native>(x: R, r: R) -> R {
578 if x.is_nan() { x } else if x.abs() < R::narrow(28.0) { r } else if x < R::zero() { R::narrow(2.0) } else { R::zero() }
579}
580
581const GILES: [f64; 9] =
583 [2.81022636e-08, 3.43273939e-07, -3.5233877e-06, -4.39150654e-06, 0.00021858087, -0.00125372503, -0.00417768164, 0.246640727, 1.50140941];
584const GILES_TAIL: [f64; 9] =
585 [-0.000200214257, 0.000100950558, 0.00134934322, -0.00367342844, 0.00573950773, -0.0076224613, 0.00943887047, 1.00167406, 2.83297682];
586const GILES_FAR: [f64; 11] = [
588 3.1297268653862105e-10, -2.430591888561262e-09, 7.967095370797346e-09, -4.234119381819269e-08, 3.1378449112095126e-07, -1.6691356665594907e-06,
589 6.066115530379893e-06, 6.369503371198156e-06, -0.0005126738823338601, 1.008595581302738, 6.869019655969833,
590];
591
592#[inline(always)]
594fn giles<R: Native, const FUSED: bool>(p: R, d: R) -> R {
595 let w = -ln::<R, FUSED>(d);
596 let central = || horner::<R, FUSED, 9>(w - R::narrow(2.5), GILES);
597 let tail = || horner::<R, FUSED, 9>(w.sqrt() - R::narrow(3.0), GILES_TAIL);
598 let far = || horner::<R, FUSED, 11>(w.sqrt() - R::narrow(7.0), GILES_FAR);
599 let x = p * either(w < R::narrow(5.0), central, || either(w < R::narrow(16.0), tail, far));
600 if d == R::zero() { R::infinity().copysign(p) } else { x }
601}
602
603#[inline(always)]
605pub(crate) fn erfinv<R: Native, const FUSED: bool>(p: R) -> R {
606 let one = R::one();
607 if R::WIDE { R::narrow(erfinv64::<FUSED>(p.wide())) } else { giles::<R, FUSED>(p, (one - p) * (one + p)) }
608}
609
610#[inline(always)]
612pub(crate) fn erfcinv<R: Native, const FUSED: bool>(q: R) -> R {
613 let one = R::one();
614 if R::WIDE { R::narrow(erfcinv64::<FUSED>(q.wide())) } else { giles::<R, FUSED>(one - q, q * (R::narrow(2.0) - q)) }
615}
616
617#[inline(always)]
618fn erfinv64<const FUSED: bool>(p: f64) -> f64 {
619 let a = p.abs();
620 if a < 1.0 { maddock::<FUSED>(a, 1.0 - a).copysign(p) } else if a == 1.0 { f64::INFINITY.copysign(p) } else { f64::NAN }
621}
622
623#[inline(always)]
625fn erfcinv64<const FUSED: bool>(q: f64) -> f64 {
626 if !(q > 0.0 && q < 2.0) { return if q == 0.0 { f64::INFINITY } else if q == 2.0 { f64::NEG_INFINITY } else { f64::NAN }; }
627 maddock::<FUSED>((q - 1.0).abs(), q.min(2.0 - q)).copysign(1.0 - q)
628}
629
630#[derive(Clone, Copy, Debug)]
632pub struct Erfc;
633forms!(Erfc at real: |_, [x]| crate::slice::Total(erfc::<_, { crate::FUSED }>(x)),
634many |_, [x], n, out| match x {
635 Arg::Many(x) => erfc_blocks::<_, { crate::FUSED }>(&x[..n], out),
636 x => each!([x], n, out, |[x]| crate::slice::Total(erfc::<_, { crate::FUSED }>(x))),
637});
638
639fn erfc_blocks<R: Native, const FUSED: bool>(x: &[R], out: &mut [MaybeUninit<R>]) -> bool {
641 const BLOCK: usize = 512;
642 let (mut place, mut t) = ([const { MaybeUninit::uninit() }; BLOCK], [const { MaybeUninit::uninit() }; BLOCK]);
643 let split = R::narrow(ERFC_SPLIT);
644 #[inline(always)]
645 fn near<R: Native, const FUSED: bool>(v: R) -> R { erfc_ends(v, erfc_near::<R, FUSED>(v)) }
646 #[inline(always)]
647 fn far<R: Native, const FUSED: bool>(v: R) -> R { erfc_ends(v, erfc_far::<R, FUSED>(v)) }
648 crate::slice::blocks::<_, _, BLOCK>(x, out, #[inline(always)] |b| {
649 let (low, high) = b.iter().fold((R::infinity(), R::zero()), |(low, high), &v| (low.min(v.abs()), high.max(v.abs())));
650 (high < split, low >= split)
651 }, near::<R, FUSED>, far::<R, FUSED>, #[inline(always)] |xs, os| {
652 if branches::<R>() {
653 let n = placed(0..xs.len() as u16, &mut place, |i| xs[usize::from(i)].abs() < split);
654 let (nears, fars) = unsafe { written(&mut place[..xs.len()]) }.split_at(n);
655 gathered(nears, &mut t, os, |i| xs[i], #[inline(always)] |t| {
656 for v in t { *v = near::<R, FUSED>(*v) }
657 }, |_, v| v);
658 gathered(fars, &mut t, os, |i| xs[i], #[inline(always)] |t| {
659 for v in t { *v = far::<R, FUSED>(*v) }
660 }, |_, v| v);
661 } else { for (o, &v) in os.iter_mut().zip(xs) { o.write(erfc::<R, FUSED>(v)); } }
662 true
663 })
664}
665#[derive(Clone, Copy, Debug)]
667pub struct Erfinv;
668forms!(Erfinv at real: |_, [p]| erfinv::<_, { crate::FUSED }>(p),
669 many |_, [p], n, out| inverses(p, n, out, #[inline(always)] |p| erfinv::<_, { crate::FUSED }>(p), #[inline(always)] |p| (p.abs(), 1.0 - p.abs(), p)));
670
671#[derive(Clone, Copy, Debug)]
673pub struct Erfcinv;
674forms!(Erfcinv at real: |_, [q]| erfcinv::<_, { crate::FUSED }>(q),
675 many |_, [q], n, out| inverses(q, n, out, #[inline(always)] |q| erfcinv::<_, { crate::FUSED }>(q), #[inline(always)] |q| ((q - 1.0).abs(), q.min(2.0 - q), 1.0 - q)));
676
677fn inverses<R: Native>(x: Arg<'_, R>, n: usize, out: &mut [MaybeUninit<R>], f: impl Fn(R) -> R, parts: impl Fn(f64) -> (f64, f64, f64)) -> bool {
679 match x { Arg::Many(x) if R::WIDE => regions::<R, { crate::FUSED }>(&x[..n], out, f, parts), x => each!([x], n, out, |[x]| f(x)) }
680}
681
682fn regions<R: Native, const FUSED: bool>(x: &[R], out: &mut [MaybeUninit<R>], f: impl Fn(R) -> R, parts: impl Fn(f64) -> (f64, f64, f64)) -> bool {
684 const BLOCK: usize = 512;
685 let (mut place, mut split) = ([const { MaybeUninit::uninit() }; BLOCK], [const { MaybeUninit::uninit() }; BLOCK]);
686 let (mut ps, mut qs, mut signs, mut t) = ([const { MaybeUninit::uninit() }; BLOCK], [const { MaybeUninit::uninit() }; BLOCK],
687 [const { MaybeUninit::uninit() }; BLOCK], [const { MaybeUninit::uninit() }; BLOCK]);
688 let mut ok = true;
689 for (xs, os) in x.chunks(BLOCK).zip(out.chunks_mut(BLOCK)) {
690 let mut outside = false;
691 let central = placed(0..xs.len() as u16, &mut place, |i| {
692 let i = usize::from(i);
693 let (p, q, sign) = parts(xs[i].wide());
694 (ps[i], qs[i], signs[i]) = (MaybeUninit::new(p), MaybeUninit::new(q), MaybeUninit::new(sign));
695 outside |= !(p < 1.0);
696 p <= 0.5
697 });
698 let (ps, qs, signs): (&[f64], &[f64], &[f64]) =
699 (unsafe { written(&mut ps[..xs.len()]) }, unsafe { written(&mut qs[..xs.len()]) }, unsafe { written(&mut signs[..xs.len()]) });
700 let (central, others) = unsafe { written(&mut place[..xs.len()]) }.split_at(central);
701 let upper = placed(others.iter().copied(), &mut split, |i| qs[usize::from(i)] >= 0.25);
702 let (upper, below) = unsafe { written(&mut split[..others.len()]) }.split_at(upper);
703 let signed = |i: usize, v: f64| R::narrow(v.copysign(signs[i]));
704 gathered(central, &mut t, os, |i| ps[i], #[inline(always)] |t| {
705 for v in t { *v = maddock_central::<FUSED>(*v) }
706 }, signed);
707 gathered(upper, &mut t, os, |i| qs[i], #[inline(always)] |t| {
708 for v in t { *v = maddock_upper::<FUSED>(*v) }
709 }, signed);
710 gathered(below, &mut t, os, |i| qs[i], #[inline(always)] |t| {
711 for v in t.iter_mut() { *v = maddock_g::<FUSED>(*v) }
712 for v in t { *v = maddock_below::<FUSED>(*v) }
713 }, signed);
714 if outside {
715 for (i, (&v, o)) in xs.iter().zip(os.iter_mut()).enumerate() {
716 if !(ps[i] < 1.0) {
717 let r = f(v);
718 o.write(r);
719 ok &= !r.is_nan() | v.is_nan();
720 }
721 }
722 }
723 }
724 ok
725}
726
727#[inline(always)]
729fn gathered<T: Copy, R>(
730 positions: &[u16], t: &mut [MaybeUninit<T>], os: &mut [MaybeUninit<R>], arg: impl Fn(usize) -> T, formula: impl Fn(&mut [T]), result: impl Fn(usize, T) -> R
731) {
732 let t = &mut t[..positions.len()];
733 for (v, &i) in t.iter_mut().zip(positions) { v.write(arg(usize::from(i))); }
734 let t = unsafe { written(t) };
735 formula(t);
736 for (&v, &i) in t.iter().zip(positions) {
737 let i = usize::from(i);
738 os[i].write(result(i, v));
739 }
740}
741
742#[inline(always)]
744fn placed(positions: impl ExactSizeIterator<Item = u16>, place: &mut [MaybeUninit<u16>], mut first: impl FnMut(u16) -> bool) -> usize {
745 let (mut lo, mut hi) = (0, positions.len());
746 for i in positions {
747 let front = first(i);
748 place[lo].write(i);
749 place[hi - 1].write(i);
750 lo += usize::from(front);
751 hi -= usize::from(!front);
752 }
753 lo
754}
755
756#[inline(always)]
758fn maddock<const FUSED: bool>(p: f64, q: f64) -> f64 {
759 if p <= 0.5 { maddock_central::<FUSED>(p) } else if q >= 0.25 { maddock_upper::<FUSED>(q) } else { maddock_below::<FUSED>(maddock_g::<FUSED>(q)) }
760}
761
762#[inline(always)]
764fn maddock_central<const FUSED: bool>(p: f64) -> f64 { erfinv_regions::CENTRAL.scaled::<FUSED>(p * (p + 10.0), p) }
765
766#[inline(always)]
768fn maddock_upper<const FUSED: bool>(q: f64) -> f64 { use erfinv_regions::UPPER; (-2.0 * ln::<f64, FUSED>(q)).sqrt() / (UPPER.y + UPPER.ratio::<FUSED>(q)) }
769
770#[inline(always)]
772fn maddock_g<const FUSED: bool>(q: f64) -> f64 { (-ln::<f64, FUSED>(q)).sqrt() }
773
774#[inline(always)]
776fn maddock_below<const FUSED: bool>(g: f64) -> f64 {
777 use erfinv_regions::*;
778 if g < 3.0 { BELOW_3.scaled::<FUSED>(g, g) }
779 else if g < 6.0 { BELOW_6.scaled::<FUSED>(g, g) }
780 else if g < 18.0 { BELOW_18.scaled::<FUSED>(g, g) }
781 else { BELOW_44.scaled::<FUSED>(g, g) }
782}
783
784struct Region<const P: usize, const Q: usize> { y: f64, shift: f64, p: [f64; P], q: [f64; Q] }
786
787impl<const P: usize, const Q: usize> Region<P, Q> {
788 #[inline(always)]
790 fn ratio<const FUSED: bool>(&self, x: f64) -> f64 { let s = x - self.shift; horner::<f64, FUSED, P>(s, self.p) / horner::<f64, FUSED, Q>(s, self.q) }
791
792 #[inline(always)]
794 fn scaled<const FUSED: bool>(&self, g: f64, x: f64) -> f64 { g.mla::<FUSED>(self.ratio::<FUSED>(x), g * self.y) }
795}
796
797mod erfinv_regions {
799 use super::Region;
800 pub(super) const CENTRAL: Region<8, 10> = Region { y: 0.08913147449493408, shift: 0.0, p: [
801 -0.005387729650712429, 0.008226878746769157, 0.02198786811111689, -0.03656379714117627, -0.012692614766297404, 0.03348066254097446,
802 -0.008368748197417368, -0.0005087819496582806,
803 ], q: [
804 0.0008862163904564247, -0.0023339375937419, 0.07952836873415717, -0.05273963823400997, -0.7122890234154284, 0.662328840472003, 1.5622155839842302,
805 -1.5657455823417585, -0.9700050433032906, 1.0,
806 ] };
807 pub(super) const UPPER: Region<9, 9> = Region { y: 2.249481201171875, shift: 0.25, p: [
808 -3.6719225470772936, 21.12946554483405, 17.445385985570866, -44.6382324441787, -18.851064805871424, 17.644729840837403, 8.3705032834312,
809 0.10526468069939171, -0.20243350835593876,
810 ], q: [
811 1.7211476576120028, -22.643693341313973, 10.826866735546016, 48.560921310873994, -20.14326346804852, -28.66081804998, 3.971343795334387,
812 6.242641248542475, 1.0,
813 ] };
814 pub(super) const BELOW_3: Region<11, 8> = Region { y: 0.807220458984375, shift: 1.125, p: [
815 -6.81149956853777e-10, 2.8522533178221704e-08, -6.794655751811263e-07, 0.0021455899538880526, 0.029015791000532906, 0.14286953440815717,
816 0.3377855389120359, 0.38707973897260434, 0.11703015634199525, -0.16379404719331705, -0.1311027816799519,
817 ], q: [0.011059242293464892, 0.15226433829533179, 0.848854343457902, 2.5930192162362027, 4.778465929458438, 5.381683457070069, 3.4662540724256723, 1.0] };
818 pub(super) const BELOW_6: Region<9, 7> = Region { y: 0.9399557113647461, shift: 3.0, p: [
819 2.6633922742578204e-12, -2.304047769118826e-10, 4.60469890584318e-06, 0.00015754461742496055, 0.0018712349281955923, 0.009508047013259196,
820 0.018557330651423107, -0.0022242652921344794, -0.0350353787183178,
821 ], q: [7.646752923027944e-05, 0.00263861676657016, 0.03415891436709477, 0.22009110576413124, 0.7620591645536234, 1.3653349817554064, 1.0] };
822 pub(super) const BELOW_18: Region<9, 7> = Region { y: 0.9836282730102539, shift: 6.0, p: [
823 9.905570997331033e-17, -2.811287356288318e-14, 4.625961635228786e-09, 4.4969678992770644e-07, 1.4962478375834237e-05, 0.00020938631748758808,
824 0.001056288621524929, -0.0011295143874558028, -0.016743100507663373,
825 ], q: [2.82243172016108e-07, 2.7533547476472603e-05, 0.0009640118070051656, 0.016074608709367652, 0.1381518657490833, 0.5914293448864175, 1.0] };
826 pub(super) const BELOW_44: Region<8, 7> = Region { y: 0.9971456527709961, shift: 18.0, p: [
827 -1.1676501239718427e-18, 1.4559628671867504e-12, 4.116328311909442e-10, 3.963410113048012e-08, 1.6239777734251093e-06, 2.5472303741302746e-05,
828 -7.79190719229054e-06, -0.002497821279189813,
829 ], q: [5.097612765997785e-10, 1.4443775662814415e-07, 1.4500735981823264e-05, 0.0006905382656226846, 0.01694108381209759, 0.2071231122144225, 1.0] };
830}
831
832const STEPS: usize = 100_000;
834const TINY: f64 = 1e-300;
836const NARROW_EPS: f64 = 1.0 / (1u64 << 26) as f64;
838const RESCALE: f64 = 4503599627370496.0;
840
841fn gamma_factor<const FUSED: bool>(a: f64, x: f64) -> f64 {
843 let t = ln_dd::<FUSED>(Double::new(x, 0.0), true).mul_float::<FUSED>(a).plus(Double::new(-x, 0.0));
844 let t = t.plus(-lgamma_dd::<FUSED>(Double::new(a, 0.0)));
845 exp_plus::<f64, FUSED>(t)
846}
847
848fn gamma_factor_plain<const FUSED: bool>(a: f64, x: f64) -> f64 {
850 let (z, p) = shifted(a);
851 let w = 1.0 / z;
852 let u = if z > a {
853 let w2 = w * w;
854 let w4 = w2 * w2;
855 p * (w4 * w4)
856 } else { 1.0 };
857 let half_log = 0.5 * ln_narrow::<FUSED>(z * u * u);
858 let e = a.mla::<FUSED>(ln::<f64, FUSED>(x * w), half_log + (z - x) - stirling_tail::<FUSED>(w));
859 exp_narrow::<FUSED>(e)
860}
861
862fn incomplete_gamma<const FUSED: bool>(a: f64, x: f64, wide: bool) -> (f64, f64) {
864 if !(a > 0.0 && a.is_finite() && x >= 0.0) { return (f64::NAN, f64::NAN); }
865 if x == 0.0 { return (0.0, 1.0); }
866 if x.is_infinite() { return (1.0, 0.0); }
867 let eps = if wide { f64::EPSILON } else { NARROW_EPS };
868 let factor = || if wide { gamma_factor::<FUSED>(a, x) } else { gamma_factor_plain::<FUSED>(a, x) };
869 if x < a + 1.0 {
870 let (mut term, mut sum, mut n) = (1.0 / a, 1.0 / a, a);
872 for _ in 0..STEPS {
873 let r = x / ((n + 1.0) * (n + 2.0));
874 let first = term * r * (n + 2.0);
875 term *= r * x;
876 sum += first + term;
877 n += 2.0;
878 if term < sum * eps { break; }
879 }
880 let p = sum * factor();
881 return (p, 1.0 - p);
882 }
883 let (mut y, mut z) = (1.0 - a, x + 2.0 - a);
885 let (mut p0, mut q0, mut p1, mut q1) = (1.0, x, x + 1.0, z * x);
886 let mut h = p1 / q1;
887 for i in (1..STEPS).step_by(2) {
888 for k in i..i + 2 {
889 y += 1.0;
890 z += 2.0;
891 let yk = y * k as f64;
892 (p0, q0, p1, q1) = (p1, q1, p1 * z - p0 * yk, q1 * z - q0 * yk);
893 }
894 if p1.abs() > RESCALE { (p0, q0, p1, q1) = (p0 / RESCALE, q0 / RESCALE, p1 / RESCALE, q1 / RESCALE); }
896 if q1 != 0.0 {
897 let r = p1 / q1;
898 let done = (h - r).abs() <= eps * r.abs();
899 h = r;
900 if done { break; }
901 }
902 }
903 let q = h * factor();
904 (1.0 - q, q)
905}
906
907#[inline(always)]
909pub(crate) fn gammainc<R: Native, const FUSED: bool>(a: R, x: R) -> R { R::narrow(incomplete_gamma::<FUSED>(a.wide(), x.wide(), R::WIDE).0) }
910
911#[inline(always)]
912pub(crate) fn gammaincc<R: Native, const FUSED: bool>(a: R, x: R) -> R { R::narrow(incomplete_gamma::<FUSED>(a.wide(), x.wide(), R::WIDE).1) }
913
914fn gammainc64<const FUSED: bool>(a: f64, x: f64) -> f64 { incomplete_gamma::<FUSED>(a, x, true).0 }
915
916#[inline]
918fn halley(x: f64, residual: f64, slope: f64, curve: f64) -> f64 { let u = residual / slope; x - u / (1.0 - 0.5 * (u * curve).min(1.0)) }
919const SETTLED: f64 = 1.5e-8;
920
921fn normal_guess<const FUSED: bool>(p: f64) -> f64 {
923 let t = (-2.0 * ln::<f64, FUSED>(p.min(1.0 - p))).sqrt();
924 let z = (2.30753 + t * 0.27061) / (1.0 + t * (0.99229 + t * 0.04481)) - t;
925 if p < 0.5 { -z } else { z }
926}
927
928fn gammaincinv64<const FUSED: bool>(a: f64, p: f64) -> f64 {
930 if !(a > 0.0 && a.is_finite() && (0.0..=1.0).contains(&p)) { return f64::NAN; }
931 if p == 0.0 { return 0.0; }
932 if p == 1.0 { return f64::INFINITY; }
933 let lg = lgamma64::<FUSED>(a);
934 let a1 = a - 1.0;
935 let mut x = if a > 1.0 {
936 let z = normal_guess::<FUSED>(p);
937 let c = 1.0 - 1.0 / (9.0 * a) - z / (3.0 * a.sqrt());
938 (a * c * c * c).max(1e-3)
939 } else {
940 let t = 1.0 - a * (0.253 + a * 0.12);
941 if p < t { exp::<f64, FUSED>(ln::<f64, FUSED>(p / t) / a) } else { 1.0 - ln::<f64, FUSED>(1.0 - (p - t) / (1.0 - t)) }
942 };
943 for _ in 0..12 {
944 if x <= 0.0 { return 0.0; }
945 let lx = ln::<f64, FUSED>(x);
946 let density = exp::<f64, FUSED>(a1 * lx - x - lg);
947 let next = halley(x, gammainc64::<FUSED>(a, x) - p, density, a1 / x - 1.0);
948 let step = x - next;
949 x = if next <= 0.0 { 0.5 * x } else { next };
950 if step.abs() < SETTLED * x { break; }
951 }
952 x
953}
954
955fn beta_fraction(a: f64, b: f64, x: f64) -> f64 {
957 let clamp = |v: f64| if v.abs() < TINY { TINY } else { v };
958 let (mut c, mut d) = (1.0, 1.0 / clamp(1.0 - (a + b) * x / (a + 1.0)));
959 let mut h = d;
960 for m in 1..STEPS {
961 let (m, m2) = (m as f64, 2.0 * m as f64);
962 let even = m * (b - m) * x / ((a - 1.0 + m2) * (a + m2));
963 d = 1.0 / clamp(1.0 + even * d);
964 c = clamp(1.0 + even / c);
965 h *= d * c;
966 let odd = -(a + m) * (a + b + m) * x / ((a + m2) * (a + 1.0 + m2));
967 d = 1.0 / clamp(1.0 + odd * d);
968 c = clamp(1.0 + odd / c);
969 let step = d * c;
970 h *= step;
971 if (step - 1.0).abs() < f64::EPSILON { break; }
972 }
973 h
974}
975
976#[derive(Clone, Copy, Debug)]
978pub struct BetaincAt(pub f64, pub f64);
979
980fn lbeta<const FUSED: bool>(a: f64, b: f64) -> f64 { lgamma64::<FUSED>(a) + lgamma64::<FUSED>(b) - lgamma64::<FUSED>(a + b) }
982
983fn betainc64<const FUSED: bool>(a: f64, b: f64, x: f64) -> f64 {
985 if !(a > 0.0 && b > 0.0 && a.is_finite() && b.is_finite() && (0.0..=1.0).contains(&x)) { return f64::NAN; }
986 if x == 0.0 || x == 1.0 { return x; }
987 let (one, sum) = (Double::new(1.0, 0.0), Double::sum(a, b));
989 let (lx, l1x) = (ln_dd::<FUSED>(Double::new(x, 0.0), true), ln_dd::<FUSED>(one.plus(Double::new(-x, 0.0)), true));
990 let t = lx.mul_float::<FUSED>(a).plus(l1x.mul_float::<FUSED>(b));
991 let t = t.plus(lgamma_dd::<FUSED>(sum)).plus(-lgamma_dd::<FUSED>(Double::new(a, 0.0)).plus(lgamma_dd::<FUSED>(Double::new(b, 0.0))));
992 let front = exp_plus::<f64, FUSED>(t);
993 if x < (a + 1.0) / (a + b + 2.0) { front * beta_fraction(a, b, x) / a } else { 1.0 - front * beta_fraction(b, a, 1.0 - x) / b }
994}
995
996fn betaincinv64<const FUSED: bool>(a: f64, b: f64, p: f64) -> f64 {
998 if !(a > 0.0 && b > 0.0 && a.is_finite() && b.is_finite() && (0.0..=1.0).contains(&p)) { return f64::NAN; }
999 if p == 0.0 || p == 1.0 { return p; }
1000 let x = beta_guess::<FUSED>(a, b, p);
1001 if x > 0.5 { 1.0 - beta_steps::<FUSED>(b, a, 1.0 - p, 1.0 - x) } else { beta_steps::<FUSED>(a, b, p, x) }
1002}
1003
1004fn beta_guess<const FUSED: bool>(a: f64, b: f64, p: f64) -> f64 {
1006 if a >= 1.0 && b >= 1.0 {
1007 let z = normal_guess::<FUSED>(p);
1008 let al = (z * z - 3.0) / 6.0;
1009 let h = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0));
1010 let w = z * (al + h).sqrt() / h - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) * (al + 5.0 / 6.0 - 2.0 / (3.0 * h));
1011 a / (a + b * exp::<f64, FUSED>(2.0 * w))
1012 } else {
1013 let (la, lb) = (ln::<f64, FUSED>(a / (a + b)), ln::<f64, FUSED>(b / (a + b)));
1014 let (t, u) = (exp::<f64, FUSED>(a * la) / a, exp::<f64, FUSED>(b * lb) / b);
1015 let w = t + u;
1016 if p < t / w { exp::<f64, FUSED>(ln::<f64, FUSED>(a * w * p) / a) } else { 1.0 - exp::<f64, FUSED>(ln::<f64, FUSED>(b * w * (1.0 - p)) / b) }
1017 }
1018}
1019
1020fn beta_steps<const FUSED: bool>(a: f64, b: f64, p: f64, mut x: f64) -> f64 {
1022 let (a1, b1, lb) = (a - 1.0, b - 1.0, lbeta::<FUSED>(a, b));
1023 for _ in 0..20 {
1024 if x <= 0.0 || x >= 1.0 { return x.clamp(0.0, 1.0); }
1025 let density = exp::<f64, FUSED>(a1 * ln::<f64, FUSED>(x) + b1 * log1p::<f64, FUSED>(-x) - lb);
1026 let next = halley(x, betainc64::<FUSED>(a, b, x) - p, density, a1 / x - b1 / (1.0 - x));
1027 let step = x - next;
1028 x = if next <= 0.0 { 0.5 * x } else if next >= 1.0 { 0.5 * (x + 1.0) } else { next };
1029 if step.abs() < SETTLED * x { break; }
1030 }
1031 x
1032}