@numero-uno-jpn/rstats 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.ja.md +36 -20
  3. package/README.md +34 -18
  4. package/THIRD_PARTY_NOTICES.md +25 -5
  5. package/dist/chunks/chunk-2JCIB5H2.js +2 -0
  6. package/dist/chunks/chunk-7JVSDGQQ.js +2 -0
  7. package/dist/chunks/chunk-7NK4SA6B.js +2 -0
  8. package/dist/chunks/chunk-AJYQNFXK.js +2 -0
  9. package/dist/chunks/chunk-DRNMIEYH.js +2 -0
  10. package/dist/chunks/chunk-NAYDS7CL.js +2 -0
  11. package/dist/chunks/chunk-NOCEAM44.js +2 -0
  12. package/dist/chunks/chunk-QNAIK4PY.js +2 -0
  13. package/dist/chunks/chunk-TPC6MTYC.js +2 -0
  14. package/dist/chunks/chunk-UGUQYWRQ.js +2 -0
  15. package/dist/chunks/chunk-XM2TVD63.js +2 -0
  16. package/dist/chunks/chunk-Y4IZV7XK.js +2 -0
  17. package/dist/chunks/chunk-ZWJ42QN6.js +2 -0
  18. package/dist/descriptive/basic.d.ts +73 -0
  19. package/dist/descriptive/basic.js +256 -0
  20. package/dist/descriptive/cov.d.ts +39 -0
  21. package/dist/descriptive/cov.js +105 -0
  22. package/dist/descriptive/hist.d.ts +64 -0
  23. package/dist/descriptive/hist.js +304 -0
  24. package/dist/descriptive/moments.d.ts +63 -0
  25. package/dist/descriptive/moments.js +158 -0
  26. package/dist/descriptive/quantile.d.ts +50 -0
  27. package/dist/descriptive/quantile.js +131 -0
  28. package/dist/descriptive/rank.d.ts +23 -0
  29. package/dist/descriptive/rank.js +72 -0
  30. package/dist/descriptive/table.d.ts +54 -0
  31. package/dist/descriptive/table.js +160 -0
  32. package/dist/descriptive.js +2 -0
  33. package/dist/distributions/arcsine.d.ts +24 -0
  34. package/dist/distributions/arcsine.js +73 -0
  35. package/dist/distributions/invgamma.d.ts +29 -0
  36. package/dist/distributions/invgamma.js +85 -0
  37. package/dist/distributions/kumaraswamy.d.ts +17 -0
  38. package/dist/distributions/kumaraswamy.js +96 -0
  39. package/dist/distributions/laplace.d.ts +23 -0
  40. package/dist/distributions/laplace.js +69 -0
  41. package/dist/distributions/multinomial.d.ts +24 -0
  42. package/dist/distributions/multinomial.js +70 -0
  43. package/dist/distributions/nbeta.js +28 -10
  44. package/dist/distributions/nchisq.js +20 -3
  45. package/dist/distributions/pareto.d.ts +23 -0
  46. package/dist/distributions/pareto.js +76 -0
  47. package/dist/distributions/summary.d.ts +25 -0
  48. package/dist/distributions/summary.js +289 -0
  49. package/dist/distributions/triangular.d.ts +25 -0
  50. package/dist/distributions/triangular.js +87 -0
  51. package/dist/distributions.js +2 -0
  52. package/dist/entries/descriptive.d.ts +7 -0
  53. package/dist/entries/descriptive.js +8 -0
  54. package/dist/entries/distributions.d.ts +41 -0
  55. package/dist/entries/distributions.js +41 -0
  56. package/dist/entries/linalg.d.ts +11 -0
  57. package/dist/entries/linalg.js +11 -0
  58. package/dist/entries/numerics.d.ts +8 -0
  59. package/dist/entries/numerics.js +6 -0
  60. package/dist/entries/special.d.ts +9 -0
  61. package/dist/entries/special.js +9 -0
  62. package/dist/entries/tests.d.ts +21 -0
  63. package/dist/entries/tests.js +20 -0
  64. package/dist/htest/ansariTest.d.ts +13 -0
  65. package/dist/htest/ansariTest.js +80 -0
  66. package/dist/htest/aov.d.ts +85 -0
  67. package/dist/htest/aov.js +178 -0
  68. package/dist/htest/binomTest.d.ts +36 -0
  69. package/dist/htest/binomTest.js +121 -0
  70. package/dist/htest/cor.d.ts +14 -0
  71. package/dist/htest/cor.js +4 -3
  72. package/dist/htest/core/ansari.d.ts +18 -0
  73. package/dist/htest/core/ansari.js +121 -0
  74. package/dist/htest/core/kolmogorov.d.ts +26 -0
  75. package/dist/htest/core/kolmogorov.js +221 -0
  76. package/dist/htest/core/mantelhaen.d.ts +26 -0
  77. package/dist/htest/core/mantelhaen.js +145 -0
  78. package/dist/htest/core/shapiro.d.ts +24 -0
  79. package/dist/htest/core/shapiro.js +142 -0
  80. package/dist/htest/core/smirnov.d.ts +20 -0
  81. package/dist/htest/core/smirnov.js +75 -0
  82. package/dist/htest/fisherTest.js +134 -120
  83. package/dist/htest/groups.d.ts +20 -0
  84. package/dist/htest/groups.js +62 -0
  85. package/dist/htest/ksTest.d.ts +31 -0
  86. package/dist/htest/ksTest.js +106 -0
  87. package/dist/htest/mantelhaenTest.d.ts +26 -0
  88. package/dist/htest/mantelhaenTest.js +196 -0
  89. package/dist/htest/mcnemarTest.d.ts +26 -0
  90. package/dist/htest/mcnemarTest.js +99 -0
  91. package/dist/htest/onewayTest.d.ts +26 -0
  92. package/dist/htest/onewayTest.js +97 -0
  93. package/dist/htest/pairwise.d.ts +64 -0
  94. package/dist/htest/pairwise.js +152 -0
  95. package/dist/htest/poissonTest.d.ts +35 -0
  96. package/dist/htest/poissonTest.js +170 -0
  97. package/dist/htest/propTest.d.ts +37 -0
  98. package/dist/htest/propTest.js +164 -0
  99. package/dist/htest/rankTests.d.ts +52 -0
  100. package/dist/htest/rankTests.js +234 -0
  101. package/dist/htest/types.d.ts +2 -0
  102. package/dist/htest/types.js +6 -0
  103. package/dist/htest/varTest.d.ts +48 -0
  104. package/dist/htest/varTest.js +118 -0
  105. package/dist/htest/wilcoxTest.js +136 -174
  106. package/dist/index.d.ts +6 -50
  107. package/dist/index.js +8 -50
  108. package/dist/integrate/quadpack.d.ts +29 -0
  109. package/dist/integrate/quadpack.js +525 -0
  110. package/dist/internal/seq.d.ts +5 -0
  111. package/dist/internal/seq.js +14 -0
  112. package/dist/internal/uniroot.js +10 -71
  113. package/dist/linalg/lu.d.ts +45 -0
  114. package/dist/linalg/lu.js +88 -0
  115. package/dist/linalg/ops.d.ts +90 -0
  116. package/dist/linalg/ops.js +320 -0
  117. package/dist/linalg.js +2 -0
  118. package/dist/models/lm.d.ts +98 -0
  119. package/dist/models/lm.js +187 -0
  120. package/dist/numerics/deriv.d.ts +37 -0
  121. package/dist/numerics/deriv.js +150 -0
  122. package/dist/numerics/integrate.d.ts +91 -0
  123. package/dist/numerics/integrate.js +264 -0
  124. package/dist/numerics/interp.d.ts +103 -0
  125. package/dist/numerics/interp.js +581 -0
  126. package/dist/numerics/ode.d.ts +23 -0
  127. package/dist/numerics/ode.js +59 -0
  128. package/dist/numerics.js +2 -0
  129. package/dist/optim/localmin.d.ts +16 -0
  130. package/dist/optim/localmin.js +97 -0
  131. package/dist/random/beta.js +1 -1
  132. package/dist/random/binomial.js +1 -1
  133. package/dist/random/continuous.js +1 -1
  134. package/dist/random/extra.d.ts +33 -0
  135. package/dist/random/extra.js +62 -0
  136. package/dist/random/gamma.js +1 -1
  137. package/dist/random/hyper.js +1 -1
  138. package/dist/random/mt.js +0 -1
  139. package/dist/random/poisson.js +0 -1
  140. package/dist/random/rank.js +1 -1
  141. package/dist/random/rng.js +1 -1
  142. package/dist/random/sample.js +1 -1
  143. package/dist/rstats.js +2 -2
  144. package/dist/special/besselIK.d.ts +38 -0
  145. package/dist/special/besselIK.js +315 -0
  146. package/dist/special/besselJY.d.ts +32 -0
  147. package/dist/special/besselJY.js +508 -0
  148. package/dist/special/besselSeries.d.ts +8 -0
  149. package/dist/special/besselSeries.js +24 -0
  150. package/dist/special/beta.js +22 -5
  151. package/dist/special/choose.d.ts +75 -0
  152. package/dist/special/choose.js +233 -0
  153. package/dist/special/erf.d.ts +28 -2
  154. package/dist/special/erf.js +37 -10
  155. package/dist/special/extra.d.ts +47 -0
  156. package/dist/special/extra.js +220 -0
  157. package/dist/special/igam.d.ts +4 -0
  158. package/dist/special/igam.js +57 -2
  159. package/dist/special/incbet.js +77 -22
  160. package/dist/special/lgammaDiff.d.ts +7 -0
  161. package/dist/special/lgammaDiff.js +16 -3
  162. package/dist/special/psigamma.d.ts +48 -0
  163. package/dist/special/psigamma.js +318 -0
  164. package/dist/special/rCompatible.d.ts +8 -0
  165. package/dist/special/rCompatible.js +1 -0
  166. package/dist/special.js +2 -0
  167. package/dist/tests.js +2 -0
  168. package/package.json +26 -1
@@ -9,6 +9,7 @@
9
9
  // - The prefactor x^a e^-x / Gamma(a) is computed with Loader's dpoisRaw (= x^a e^-x / Gamma(a+1)),
10
10
  // to avoid cancellation in the log for large a and x
11
11
  import { erfc } from "./erf.js";
12
+ import { SPLITTER } from "./dd.js";
12
13
  import { MACHEP } from "./constants.js";
13
14
  import { lgamma, lgamma1p } from "./gamma.js";
14
15
  import { IGAM_ASYMP_COEFF_D, IGAM_ASYMP_COEFF_K, IGAM_ASYMP_COEFF_N } from "./igamAsympCoeff.js";
@@ -34,7 +35,10 @@ function igamSeriesBoth(a, x) {
34
35
  const d = dpoisRawBoth(a, x);
35
36
  return [d[0] + Math.log(ans), d[1] * ans];
36
37
  }
37
- function igamSeriesSum(a, x) {
38
+ /** sum_n x^n / ((a + 1) ... (a + n)): P(a, x) = x^a e^-x / Gamma(a + 1) times this */
39
+ export function igamSeriesSum(a, x) {
40
+ if (a > 1000 && x > 0.97 * a)
41
+ return igamSeriesSumDD(a, x);
38
42
  let r = a;
39
43
  let c = 1;
40
44
  let ans = 1;
@@ -47,6 +51,56 @@ function igamSeriesSum(a, x) {
47
51
  }
48
52
  return ans;
49
53
  }
54
+ /**
55
+ * igamSeriesSum in double-double, for large a with x above 0.97 a (the mpmath audit needs it from about there; below,
56
+ * the series is short enough for double). The terms then shrink slowly (ratio x / (a + n) close to 1, thousands
57
+ * of terms), and in double precision the roundings of the ratios, correlated between neighbouring terms, add up to
58
+ * 2e-14 relative; stopping at c <= eps ans also leaves out a tail of up to c (a + n) / (a + n - x). Written out on
59
+ * scalars (no arrays), as the loop is long
60
+ */
61
+ const TAIL = 2 ** -64;
62
+ function igamSeriesSumDD(a, x) {
63
+ let ch = 1;
64
+ let cl = 0;
65
+ let ah = 1;
66
+ let al = 0;
67
+ for (let i = 1; i < MAXITER; i++) {
68
+ // d = a + i exactly (dh + dl)
69
+ const dh = a + i;
70
+ const bb = dh - a;
71
+ const dl = a - (dh - bb) + (i - bb);
72
+ // q = x / d: q1 + q2
73
+ const q1 = x / dh;
74
+ let t = SPLITTER * q1;
75
+ const q1h = t - (t - q1);
76
+ const q1l = q1 - q1h;
77
+ t = SPLITTER * dh;
78
+ const dhh = t - (t - dh);
79
+ const dhl = dh - dhh;
80
+ const p = q1 * dh;
81
+ const pe = q1h * dhh - p + q1h * dhl + q1l * dhh + q1l * dhl;
82
+ const q2 = (x - p - pe - q1 * dl) / dh;
83
+ // c = c q
84
+ t = SPLITTER * ch;
85
+ const chh = t - (t - ch);
86
+ const chl = ch - chh;
87
+ const m = ch * q1;
88
+ const me = chh * q1h - m + chh * q1l + chl * q1h + chl * q1l + (ch * q2 + cl * q1);
89
+ ch = m + me;
90
+ cl = me - (ch - m);
91
+ // ans += c
92
+ const s = ah + ch;
93
+ const sb = s - ah;
94
+ const e = ah - (s - sb) + (ch - sb) + (al + cl);
95
+ ah = s + e;
96
+ al = e - (ah - s);
97
+ // the rest is at most c q / (1 - q) (q decreasing); stop once that is below 2^-64 ans, well under the
98
+ // rounding of the double result
99
+ if (ch * q1 <= TAIL * ah * (1 - q1))
100
+ break;
101
+ }
102
+ return ah + al;
103
+ }
50
104
  /** Continued fraction for Q(a, x) (DLMF 8.9.2) */
51
105
  function igamcContinuedFraction(a, x, log) {
52
106
  const ans = igamcContinuedFractionValue(a, x);
@@ -61,7 +115,8 @@ function igamcContinuedFractionBoth(a, x) {
61
115
  const d = dpoisRawBoth(a, x);
62
116
  return [Math.log(a) + d[0] + Math.log(ans), a * d[1] * ans];
63
117
  }
64
- function igamcContinuedFractionValue(a, x) {
118
+ /** The continued fraction of DLMF 8.9.2: Q(a, x) = x^a e^-x / Gamma(a) times this */
119
+ export function igamcContinuedFractionValue(a, x) {
65
120
  let y = 1 - a;
66
121
  let z = x + y + 1;
67
122
  let c = 0;
@@ -12,8 +12,8 @@
12
12
  // Loader's dbinomRaw (avoids cancellation in the log for large a, b)
13
13
  // - The log is formed directly from the result of each computation
14
14
  import { MACHEP } from "./constants.js";
15
- import { dbinomRawDD } from "./loader.js";
16
- import { ddAdd, ddDiv, ddLog, ddMul, ddMulNum, twoProd, twoSum } from "./dd.js";
15
+ import { dbinomExponent, dbinomRawDD } from "./loader.js";
16
+ import { ddAdd, ddDiv, ddLog, ddMul, ddMulNum, ddSub, twoProd, twoSum } from "./dd.js";
17
17
  import { lgamma1p } from "./gamma.js";
18
18
  import { lgammaDiff } from "./lgammaDiff.js";
19
19
  const BIG = 4.503599627370496e15;
@@ -33,6 +33,21 @@ function prefactor(a, b, x, xc) {
33
33
  }
34
34
  /** Lower limit of normal numbers. If the prefactor is larger, multiply on the linear scale */
35
35
  const NORMAL_MIN = 2.2250738585072014e-308 * 1e3;
36
+ /**
37
+ * m x^a (1-x)^b / B(a, b) for a prefactor below NORMAL_MIN (m a moderate factor such as the series sum).
38
+ * Going through the log would round an exponent near -708 to 0.5 ulp, which is 1e-13 relative after exp,
39
+ * so the exponent of dbinom(a; a+b, x) is kept in double-double and exp is taken once at the end.
40
+ * (multiplying by exp(-700) last rounds once, so a subnormal result is still correctly rounded).
41
+ * Returns undefined when the result underflows to 0 (the caller then works on the log scale)
42
+ */
43
+ function smallPrefactorTimes(a, b, x, xc, m) {
44
+ const { e } = dbinomExponent(a, b, x, xc);
45
+ const c = (m * ((a * b) / (a + b))) / Math.sqrt(2 * Math.PI * a * (b / (a + b)));
46
+ if (!(e[0] < 1400) || !Number.isFinite(c) || c === 0)
47
+ return undefined;
48
+ const v = Math.exp(700 - e[0]) * (1 - e[1]) * c * Math.exp(-700);
49
+ return v > 0 ? v : undefined;
50
+ }
36
51
  /**
37
52
  * Series sum S = Σ_{n>=1} (1-b)_n / n! x^n / (a + n) (the core of xsf's pseries).
38
53
  * xsf stops on the relative error of the sum including 1/a; here we stop on S itself
@@ -98,6 +113,9 @@ function pseries(a, b, xd, xcd, log) {
98
113
  const pf = prefactor(a, b, xd, xcd);
99
114
  if (pf > NORMAL_MIN)
100
115
  return (s * pf) / Math.exp(b * l1m);
116
+ const v = smallPrefactorTimes(a, b, xd, xcd, s / Math.exp(b * l1m));
117
+ if (v !== undefined)
118
+ return v;
101
119
  }
102
120
  const lv = Math.log(s) + logPrefactor(a, b, xd, xcd) - b * l1m;
103
121
  return log ? lv : Math.exp(lv);
@@ -110,32 +128,47 @@ function pseries(a, b, xd, xcd, log) {
110
128
  * the rounding of x = 0.999 (1e-19) alone shifts the result by 6e-13 relative (measured at a = 1e5, b = 100;
111
129
  * reference values checked with Rmpfr). So x is kept as an exact double-double and the recurrence also runs in
112
130
  * double-double.
131
+ *
132
+ * For the same reason a + b and b - 1 are kept in double-double: with a ≫ b, rounding a + b loses digits of b
133
+ * (5e-11 relative at a = 1e8, b = 0.01, x = 1 - 1e-6; checked against mpmath). From 2^52 on, the steps k += 1 of
134
+ * the other coefficients would be lost as well, so there every coefficient is a double-double (huge)
113
135
  */
114
136
  function continuedFractionCore(a, b, xd, xcd, second) {
115
137
  // #1: coefficients use x; #2: coefficients use z = x/(1-x)
116
138
  const z = second ? ddDiv(xd, xcd) : xd;
139
+ const huge = Math.max(a, b) >= 2 ** 52;
140
+ // Partial numerators k1 x k2 / (k3 k4) and k5 x k6 / (k7 k8). k1, k3, k4, k7, k8 are doubles, with
141
+ // double-double copies (h1, ...) that are used and advanced only when huge
117
142
  let k1 = a;
118
- let k2 = second ? b - 1 : a + b;
143
+ let k2 = second ? twoSum(b, -1) : twoSum(a, b);
119
144
  let k3 = a;
120
145
  let k4 = a + 1;
121
146
  let k5 = 1;
122
- let k6 = second ? a + b : b - 1;
147
+ let k6 = second ? twoSum(a, b) : twoSum(b, -1);
123
148
  let k7 = a + 1;
124
149
  let k8 = a + 2;
150
+ let h1 = [a, 0];
151
+ let h3 = [a, 0];
152
+ let h4 = twoSum(a, 1);
153
+ let h7 = twoSum(a, 1);
154
+ let h8 = twoSum(a, 2);
155
+ const d1 = second ? -1 : 1;
125
156
  let pkm2 = [0, 0];
126
157
  let qkm2 = [1, 0];
127
158
  let pkm1 = [1, 0];
128
159
  let qkm1 = [1, 0];
129
160
  let ans = [1, 0];
130
161
  for (let n = 0; n < 100000; n++) {
131
- let xk = ddDiv(ddMulNum(ddMulNum(z, -k1), k2), twoProd(k3, k4));
132
- let pk = ddAdd(pkm1, ddMul(pkm2, xk));
133
- let qk = ddAdd(qkm1, ddMul(qkm2, xk));
162
+ let xk = huge
163
+ ? ddDiv(ddMul(ddMul(z, h1), k2), ddMul(h3, h4))
164
+ : ddDiv(ddMul(ddMulNum(z, k1), k2), twoProd(k3, k4));
165
+ let pk = ddSub(pkm1, ddMul(pkm2, xk));
166
+ let qk = ddSub(qkm1, ddMul(qkm2, xk));
134
167
  pkm2 = pkm1;
135
168
  pkm1 = pk;
136
169
  qkm2 = qkm1;
137
170
  qkm1 = qk;
138
- xk = ddDiv(ddMulNum(ddMulNum(z, k5), k6), twoProd(k7, k8));
171
+ xk = ddDiv(ddMul(ddMulNum(z, k5), k6), huge ? ddMul(h7, h8) : twoProd(k7, k8));
139
172
  pk = ddAdd(pkm1, ddMul(pkm2, xk));
140
173
  qk = ddAdd(qkm1, ddMul(qkm2, xk));
141
174
  pkm2 = pkm1;
@@ -149,21 +182,23 @@ function continuedFractionCore(a, b, xd, xcd, second) {
149
182
  if (t < MACHEP && n > 1)
150
183
  break;
151
184
  }
152
- if (second) {
153
- k1 += 1;
154
- k2 -= 1;
155
- k6 += 1;
185
+ k2 = ddAdd(k2, [d1, 0]);
186
+ k6 = ddAdd(k6, [-d1, 0]);
187
+ k5 += 1;
188
+ if (huge) {
189
+ h1 = ddAdd(h1, [1, 0]);
190
+ h3 = ddAdd(h3, [2, 0]);
191
+ h4 = ddAdd(h4, [2, 0]);
192
+ h7 = ddAdd(h7, [2, 0]);
193
+ h8 = ddAdd(h8, [2, 0]);
156
194
  }
157
195
  else {
158
196
  k1 += 1;
159
- k2 += 1;
160
- k6 -= 1;
197
+ k3 += 2;
198
+ k4 += 2;
199
+ k7 += 2;
200
+ k8 += 2;
161
201
  }
162
- k3 += 2;
163
- k4 += 2;
164
- k5 += 1;
165
- k7 += 2;
166
- k8 += 2;
167
202
  const mag = Math.abs(qk[0]) + Math.abs(pk[0]);
168
203
  if (mag > BIG) {
169
204
  pkm2 = ddMulNum(pkm2, BIGINV);
@@ -198,8 +233,12 @@ function continuedFraction(a, b, xd, xcd, log) {
198
233
  // I = x^a (1-x)^b / (a B(a, b)) * w
199
234
  if (!log) {
200
235
  const pf = prefactor(a, b, xd, xcd);
236
+ // w / a first: pf / a can fall into the subnormal range for huge a (1e-316 at a = 7e18) and lose digits
201
237
  if (pf > NORMAL_MIN)
202
- return (pf / a) * w;
238
+ return pf * (w / a);
239
+ const v = smallPrefactorTimes(a, b, xd, xcd, w / a);
240
+ if (v !== undefined)
241
+ return v;
203
242
  }
204
243
  const lv = logPrefactor(a, b, xd, xcd) - Math.log(a) + Math.log(w);
205
244
  return log ? lv : Math.exp(lv);
@@ -292,8 +331,13 @@ function betaIncCore(a, b, xd, xcd, lower, wantLog) {
292
331
  const otherDirect = () => {
293
332
  if (pseriesOk(oa, ob, ox[0]))
294
333
  return pseries(oa, ob, ox, oxc, false);
295
- if (pseriesOk(pa, pb, px[0]))
296
- return pseriesComplement(pa, pb, px, false);
334
+ if (pseriesOk(pa, pb, px[0])) {
335
+ const v = pseriesComplement(pa, pb, px, false);
336
+ // Cancellation check as for the requested tail (see below)
337
+ if (pa * Math.abs(Math.log(px[0])) > 32 * v && cfOk(oa, ob, ox[0]))
338
+ return continuedFraction(oa, ob, ox, oxc, false);
339
+ return v;
340
+ }
297
341
  if (cfOk(oa, ob, ox[0]))
298
342
  return continuedFraction(oa, ob, ox, oxc, false);
299
343
  return undefined;
@@ -308,6 +352,17 @@ function betaIncCore(a, b, xd, xcd, lower, wantLog) {
308
352
  }
309
353
  if (pseriesOk(oa, ob, ox[0])) {
310
354
  const v = pseriesComplement(oa, ob, ox, false);
355
+ // The exponent L of pseriesComplement is a sum of terms of size a |log x| that can cancel, and their rounding
356
+ // (absolute, about ε a |log x|) is then large relative to a small result (1e-14 at a = 0.01, b = 1e4, x = 1e-4).
357
+ // When a |log x| exceeds the result 32 times and the continued fraction applies in this tail's frame, use it
358
+ // instead (a lower threshold would also send cases where the series is accurate, e.g. most of pt near 0, to the
359
+ // slower continued fraction)
360
+ if (oa * Math.abs(Math.log(ox[0])) > 32 * v && cfOk(pa, pb, px[0])) {
361
+ const w = continuedFraction(pa, pb, px, pxc, false);
362
+ if (wantLog && w < TINY)
363
+ return [continuedFraction(pa, pb, px, pxc, true), w];
364
+ return finish(w, otherDirect);
365
+ }
311
366
  return finish(v, () => pseries(oa, ob, ox, oxc, false));
312
367
  }
313
368
  if (cfOk(pa, pb, px[0])) {
@@ -1,2 +1,9 @@
1
+ import { type DD } from "./dd.js";
1
2
  /** lgamma(b + a) - lgamma(b). a >= 0, b > 0 */
2
3
  export declare function lgammaDiff(a: number, b: number): number;
4
+ /**
5
+ * lgammaDiff in double-double: the term a log(B + a), which reaches hundreds for large b (exp of it would turn its
6
+ * rounding into a relative error of 1e-14 at B(0.5, 1e300)), is formed in double-double; the others are of the size
7
+ * of a or less and stay in double precision
8
+ */
9
+ export declare function lgammaDiffDD(a: number, b: number): DD;
@@ -9,11 +9,21 @@
9
9
  // D(a, B) = (B - 1/2) log1p(a/B) + a log(B + a) - a + [stirlerr(B + a) - stirlerr(B)]
10
10
  // and compute the stirlerr difference with expm1 from the differences of the Stirling series terms,
11
11
  // B^-k ((1 + a/B)^-k - 1)
12
+ import { ddAdd, ddLog, ddMulNum, twoSum } from "./dd.js";
12
13
  const S = [1 / 12, -1 / 360, 1 / 1260, -1 / 1680, 1 / 1188, -691 / 360360, 1 / 156];
13
14
  /** lgamma(b + a) - lgamma(b). a >= 0, b > 0 */
14
15
  export function lgammaDiff(a, b) {
16
+ const d = lgammaDiffDD(a, b);
17
+ return d[0] + d[1];
18
+ }
19
+ /**
20
+ * lgammaDiff in double-double: the term a log(B + a), which reaches hundreds for large b (exp of it would turn its
21
+ * rounding into a relative error of 1e-14 at B(0.5, 1e300)), is formed in double-double; the others are of the size
22
+ * of a or less and stay in double precision
23
+ */
24
+ export function lgammaDiffDD(a, b) {
15
25
  if (a === 0)
16
- return 0;
26
+ return [0, 0];
17
27
  let acc = 0;
18
28
  let B = b;
19
29
  while (B <= 15) {
@@ -21,7 +31,10 @@ export function lgammaDiff(a, b) {
21
31
  B += 1;
22
32
  }
23
33
  const l1p = Math.log1p(a / B);
24
- let d = (B - 0.5) * l1p + a * Math.log(B + a) - a;
34
+ const lead0 = a * Math.log(B + a);
35
+ // (in double precision where it is small: its rounding is then below the others', and incbet calls this often)
36
+ const lead = Math.abs(lead0) > 2 && Number.isFinite(lead0) && a < 1e300 ? ddMulNum(ddLog(twoSum(B, a)), a) : [lead0, 0];
37
+ let d = (B - 0.5) * l1p - a;
25
38
  // stirlerr(B + a) - stirlerr(B) = Σ S_k [ (B + a)^-(2k+1) - B^-(2k+1) ]
26
39
  let pw = 1 / B;
27
40
  const inv2 = 1 / (B * B);
@@ -30,5 +43,5 @@ export function lgammaDiff(a, b) {
30
43
  d += S[k] * pw * Math.expm1(-m * l1p);
31
44
  pw *= inv2;
32
45
  }
33
- return acc + d;
46
+ return ddAdd(lead, [acc + d, 0]);
34
47
  }
@@ -0,0 +1,48 @@
1
+ import type { RCompatibleOptions } from "./rCompatible.js";
2
+ /**
3
+ * Digamma function, the derivative of `lgamma`. Equivalent to R's `digamma(x)` for a single number.
4
+ *
5
+ * @param x - Argument.
6
+ * @returns psi(x) (`NaN` at zero and the negative integers).
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * digamma(1); // -0.5772156649015329 (minus Euler's constant)
11
+ * ```
12
+ */
13
+ export declare function digamma(x: number): number;
14
+ /**
15
+ * Trigamma function, the second derivative of `lgamma`. Equivalent to R's `trigamma(x)` for a single number.
16
+ *
17
+ * `trigamma(-Infinity)` is `Infinity` as in R (`NaN` with `rCompatible: false`).
18
+ *
19
+ * @param x - Argument.
20
+ * @param options - `rCompatible` (default `true`).
21
+ * @returns psi'(x) (`Infinity` at zero and the negative integers).
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * trigamma(1); // 1.6449340668482264 (pi^2 / 6)
26
+ * ```
27
+ */
28
+ export declare function trigamma(x: number, options?: RCompatibleOptions): number;
29
+ /**
30
+ * Polygamma function, the derivative of order `deriv` of `digamma`. Equivalent to R's
31
+ * `psigamma(x, deriv)` for a single number.
32
+ *
33
+ * `psigamma(-Infinity, deriv)` is `Infinity` for odd `deriv` as in R, which treats it like the poles
34
+ * (`NaN` with `rCompatible: false`). Unlike R, which returns `NaN` for `x < 0` with `deriv >= 6` and
35
+ * where the result overflows, the value is computed there.
36
+ *
37
+ * @param x - Argument.
38
+ * @param deriv - Order of the derivative, an integer from 0 to 100 (rounded half to even; default 0, digamma).
39
+ * @param options - `rCompatible` (default `true`).
40
+ * @returns psi^(deriv)(x). At zero and the negative integers, `Infinity` for odd `deriv` and `NaN` for even.
41
+ * `NaN` if `deriv` is outside 0 to 100.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * psigamma(2, 2); // -0.40411380631918853 (-2 (zeta(3) - 1))
46
+ * ```
47
+ */
48
+ export declare function psigamma(x: number, deriv?: number, options?: RCompatibleOptions): number;
@@ -0,0 +1,318 @@
1
+ // Digamma and polygamma functions: digamma, trigamma, psigamma.
2
+ //
3
+ // digamma is ported from xsf include/xsf/cephes/psi.h and include/xsf/digamma.h (xsf commit c6a3afe; the
4
+ // rational approximation on [1, 2] in psi.h is from Boost.Math, (C) John Maddock 2006, BSL-1.0).
5
+ // Cephes Math Library Release 2.8, Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier.
6
+ // xsf Copyright (c) 2024, SciPy. BSD-3-Clause. See THIRD_PARTY_NOTICES.md.
7
+ //
8
+ // The derivatives (deriv >= 1) are written for rStats.js:
9
+ // psi^(n)(x) = (-1)^(n+1) n! zeta(n + 1, x) (x > 0)
10
+ // with the Hurwitz zeta function summed by Euler-Maclaurin as in xsf include/xsf/cephes/zeta.h, but scaled by
11
+ // x^(n+1) so that n! / x^(n+1) and the sum can be combined without overflow, and with direct terms up to
12
+ // a >= n + 1 so that the Bernoulli corrections converge for large n. For x < 0 the reflection formula
13
+ // psi^(n)(x) = (-1)^n psi^(n)(1 - x) - pi^(n+1) P_n(cot(pi x))
14
+ // is used, where d^n/dx^n cot(pi x) = pi^n P_n(cot(pi x)), P_0(t) = t, P_(k+1)(t) = -(1 + t^2) P_k'(t)
15
+ // (P_n has terms of one parity only, all of the same sign, so it is evaluated without cancellation).
16
+ import { roundHalfEven } from "../internal/integer.js";
17
+ import { EULER, MACHEP } from "./constants.js";
18
+ import { polevl } from "./polevl.js";
19
+ const PSI_A = [
20
+ 8.33333333333333333333e-2, -2.10927960927960927961e-2, 7.57575757575757575758e-3, -4.16666666666666666667e-3,
21
+ 3.96825396825396825397e-3, -8.33333333333333333333e-3, 8.33333333333333333333e-2,
22
+ ];
23
+ const PSI_Y = 0.99558162689208984; // a float constant in Boost
24
+ const PSI_ROOT1 = 1569415565.0 / 1073741824.0;
25
+ const PSI_ROOT2 = 381566830.0 / 1073741824.0 / 1073741824.0;
26
+ const PSI_ROOT3 = 0.9016312093258695918615325266959189453125e-19;
27
+ const PSI_P = [
28
+ -0.0020713321167745952, -0.045251321448739056, -0.28919126444774784, -0.65031853770896507, -0.32555031186804491,
29
+ 0.25479851061131551,
30
+ ];
31
+ const PSI_Q = [
32
+ -0.55789841321675513e-6, 0.0021284987017821144, 0.054151797245674225, 0.43593529692665969, 1.4606242909763515,
33
+ 2.0767117023730469, 1.0,
34
+ ];
35
+ /** Rational approximation on [1, 2]: digamma(x) = (x - root) (Y + R(x - 1)) */
36
+ function digammaImp12(x) {
37
+ let g = x - PSI_ROOT1;
38
+ g -= PSI_ROOT2;
39
+ g -= PSI_ROOT3;
40
+ const r = polevl(x - 1, PSI_P) / polevl(x - 1, PSI_Q);
41
+ return g * PSI_Y + g * r;
42
+ }
43
+ function psiAsy(x) {
44
+ let y = 0;
45
+ if (x < 1e17) {
46
+ const z = 1 / (x * x);
47
+ y = z * polevl(z, PSI_A);
48
+ }
49
+ return Math.log(x) - 0.5 / x - y;
50
+ }
51
+ /** Cephes psi */
52
+ function psi(x) {
53
+ let y = 0;
54
+ if (Number.isNaN(x) || x === Infinity)
55
+ return x;
56
+ if (x === -Infinity)
57
+ return NaN;
58
+ if (x === 0)
59
+ return NaN;
60
+ if (x < 0) {
61
+ // argument reduction before evaluating tan(pi x)
62
+ const r = x - Math.trunc(x);
63
+ if (r === 0)
64
+ return NaN;
65
+ y = -Math.PI / Math.tan(Math.PI * r);
66
+ x = 1 - x;
67
+ }
68
+ // positive integers up to 10
69
+ if (x <= 10 && x === Math.floor(x)) {
70
+ const n = x;
71
+ for (let i = 1; i < n; i++)
72
+ y += 1 / i;
73
+ return y - EULER;
74
+ }
75
+ // use the recurrence to move x into [1, 2]
76
+ if (x < 1) {
77
+ y -= 1 / x;
78
+ x += 1;
79
+ }
80
+ else if (x < 10) {
81
+ while (x > 2) {
82
+ x -= 1;
83
+ y += 1 / x;
84
+ }
85
+ }
86
+ if (x >= 1 && x <= 2)
87
+ return y + digammaImp12(x);
88
+ return y + psiAsy(x);
89
+ }
90
+ // Series around the negative root closest to 0 (xsf digamma.h; values computed with mpmath there)
91
+ const NEGROOT = -0.504083008264455409;
92
+ const NEGROOTVAL = 7.2897639029768949e-17;
93
+ /** zeta(n + 1, NEGROOT) for n = 1, 2, ... (the series coefficients), computed on first use */
94
+ let negrootZeta;
95
+ function digammaNearNegroot(x) {
96
+ negrootZeta ??= Array.from({ length: 100 }, (_, i) => hurwitzZeta(i + 2, NEGROOT));
97
+ let res = NEGROOTVAL;
98
+ let coeff = -1;
99
+ const z = x - NEGROOT;
100
+ for (let n = 1; n < 100; n++) {
101
+ coeff *= -z;
102
+ const term = coeff * negrootZeta[n - 1];
103
+ res += term;
104
+ if (Math.abs(term) < Number.EPSILON * Math.abs(res))
105
+ break;
106
+ }
107
+ return res;
108
+ }
109
+ /** Expansion coefficients (2k)! / B_2k for the Euler-Maclaurin formula (xsf cephes/zeta.h) */
110
+ const ZETA_A = [
111
+ 12.0, -720.0, 30240.0, -1209600.0, 47900160.0, -1.8924375803183791606e9, 7.47242496e10, -2.950130727918164224e12,
112
+ 1.1646782814350067249e14, -4.5979787224074726105e15, 1.8152105401943546773e17, -7.1661652561756670113e18,
113
+ ];
114
+ /**
115
+ * Hurwitz zeta function zeta(s, q) times q^s, for s > 1 and q > 0 (Euler-Maclaurin summation as in Cephes, with
116
+ * every term scaled by q^s). Direct terms are added until the term is negligible or a = q + i exceeds both 9 and s,
117
+ * so that the Bernoulli corrections, whose ratio is about ((s + 2i) / (2 pi a))^2, converge
118
+ */
119
+ function hurwitzZetaScaled(s, q) {
120
+ let sum = 1;
121
+ let a = q;
122
+ let b = 1;
123
+ let i = 0;
124
+ // Above 1e8 no direct terms: the expansion at a = q itself, q / (s - 1) + 1/2 + s / (12 q) - ... (cutting it after
125
+ // the 1/2 left out s / (12 q), 1e-14 relative at q = 1e8, s = 39)
126
+ while (q <= 1e8 && (i < 9 || a <= 9 || a < s)) {
127
+ i += 1;
128
+ a += 1;
129
+ b = Math.exp(-s * Math.log1p(i / q)); // (q / a)^s
130
+ sum += b;
131
+ if (Math.abs(b / sum) < MACHEP)
132
+ return sum;
133
+ }
134
+ const w = a;
135
+ sum += (b * w) / (s - 1);
136
+ sum -= 0.5 * b;
137
+ let fac = 1;
138
+ let k = 0;
139
+ for (let j = 0; j < 12; j++) {
140
+ fac *= s + k;
141
+ b /= w;
142
+ const t = (fac * b) / ZETA_A[j];
143
+ sum += t;
144
+ if (Math.abs(t / sum) < MACHEP)
145
+ break;
146
+ k += 1;
147
+ fac *= s + k;
148
+ b /= w;
149
+ k += 1;
150
+ }
151
+ return sum;
152
+ }
153
+ /**
154
+ * Hurwitz zeta function zeta(s, q) for integer s > 1 and any q that is not a non-positive integer, unscaled as in
155
+ * Cephes (negative q are allowed for integer s: the direct sum runs until q + i > 9). Used for the series
156
+ * coefficients around the negative root of digamma
157
+ */
158
+ function hurwitzZeta(s, q) {
159
+ let sum = Math.pow(q, -s);
160
+ let a = q;
161
+ let b = 0;
162
+ let i = 0;
163
+ while (i < 9 || a <= 9 || a < s) {
164
+ i += 1;
165
+ a += 1;
166
+ b = Math.pow(a, -s);
167
+ sum += b;
168
+ if (Math.abs(b / sum) < MACHEP)
169
+ return sum;
170
+ }
171
+ const w = a;
172
+ sum += (b * w) / (s - 1);
173
+ sum -= 0.5 * b;
174
+ let fac = 1;
175
+ let k = 0;
176
+ for (let j = 0; j < 12; j++) {
177
+ fac *= s + k;
178
+ b /= w;
179
+ const t = (fac * b) / ZETA_A[j];
180
+ sum += t;
181
+ if (Math.abs(t / sum) < MACHEP)
182
+ break;
183
+ k += 1;
184
+ fac *= s + k;
185
+ b /= w;
186
+ k += 1;
187
+ }
188
+ return sum;
189
+ }
190
+ /** psi^(n)(x) for x > 0, n >= 1: (-1)^(n+1) n! / x^(n+1) * [x^(n+1) zeta(n + 1, x)] */
191
+ function polygammaPositive(n, x) {
192
+ if (x === Infinity)
193
+ return n % 2 === 0 ? -0 : 0;
194
+ // n! / x^n as a running product (no overflow before the result does), then the sum divided by x
195
+ let p = 1;
196
+ for (let k = 1; k <= n; k++)
197
+ p *= k / x;
198
+ const v = p * (hurwitzZetaScaled(n + 1, x) / x);
199
+ return n % 2 === 0 ? -v : v;
200
+ }
201
+ /** Coefficients of P_n in t^2: P_n(t) = t^(n+1 mod 2) * sum_j c_j t^(2j), computed on first use */
202
+ const cotDerivCache = [];
203
+ function cotDerivPoly(n) {
204
+ // Dense coefficients of P_k(t) by recurrence, then keep those of the parity of n + 1
205
+ if (cotDerivCache[n] !== undefined)
206
+ return cotDerivCache[n];
207
+ let p = [0, 1]; // P_0 = t
208
+ for (let k = 0; k < n; k++) {
209
+ // P' then -(1 + t^2) P'
210
+ const d = p.slice(1).map((c, i) => c * (i + 1));
211
+ const next = new Array(d.length + 2).fill(0);
212
+ d.forEach((c, i) => {
213
+ next[i] = next[i] - c;
214
+ next[i + 2] = next[i + 2] - c;
215
+ });
216
+ p = next;
217
+ }
218
+ const parity = (n + 1) % 2;
219
+ const coef = [];
220
+ for (let i = parity; i < p.length; i += 2)
221
+ coef.push(p[i]);
222
+ cotDerivCache[n] = coef;
223
+ return coef;
224
+ }
225
+ /** P_n(t) (all coefficients of one sign, so no cancellation) */
226
+ function cotDeriv(n, t) {
227
+ const c = cotDerivPoly(n);
228
+ const t2 = t * t;
229
+ // Horner from the leading coefficient (starting from 0 would give 0 * Infinity = NaN when t^2 overflows)
230
+ let s = c[c.length - 1];
231
+ for (let j = c.length - 2; j >= 0; j--)
232
+ s = s * t2 + c[j];
233
+ return (n + 1) % 2 === 1 ? s * t : s;
234
+ }
235
+ /**
236
+ * cot(pi x), with x reduced exactly to r in [-1/2, 1/2]. The cosine is taken as sin(pi (1/2 - |r|)), where 1/2 - |r|
237
+ * is exact near 1/2, so that cot is exactly 0 at half-integers and keeps its relative accuracy near them (P_n
238
+ * multiplies the error of a cot that should be 0 by its huge linear coefficient: 1e174 instead of -6e117 for
239
+ * psigamma(-1.5, 100))
240
+ */
241
+ function cotpi(x) {
242
+ const r = x - Math.round(x);
243
+ const c = Math.sin(Math.PI * (0.5 - Math.abs(r)));
244
+ return c / Math.sin(Math.PI * r);
245
+ }
246
+ function polygamma(n, x, rCompatible) {
247
+ if (Number.isNaN(x))
248
+ return NaN;
249
+ // R treats -Infinity like the poles (it equals its own rounding)
250
+ if (x === -Infinity)
251
+ return rCompatible && n % 2 === 1 ? Infinity : NaN;
252
+ if (x <= 0 && x === Math.floor(x))
253
+ return n % 2 === 1 ? Infinity : NaN;
254
+ if (x > 0)
255
+ return polygammaPositive(n, x);
256
+ const refl = polygammaPositive(n, 1 - x);
257
+ return (n % 2 === 0 ? refl : -refl) - Math.pow(Math.PI, n + 1) * cotDeriv(n, cotpi(x));
258
+ }
259
+ /**
260
+ * Digamma function, the derivative of `lgamma`. Equivalent to R's `digamma(x)` for a single number.
261
+ *
262
+ * @param x - Argument.
263
+ * @returns psi(x) (`NaN` at zero and the negative integers).
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * digamma(1); // -0.5772156649015329 (minus Euler's constant)
268
+ * ```
269
+ */
270
+ export function digamma(x) {
271
+ if (Math.abs(x - NEGROOT) < 0.3)
272
+ return digammaNearNegroot(x);
273
+ return psi(x);
274
+ }
275
+ /**
276
+ * Trigamma function, the second derivative of `lgamma`. Equivalent to R's `trigamma(x)` for a single number.
277
+ *
278
+ * `trigamma(-Infinity)` is `Infinity` as in R (`NaN` with `rCompatible: false`).
279
+ *
280
+ * @param x - Argument.
281
+ * @param options - `rCompatible` (default `true`).
282
+ * @returns psi'(x) (`Infinity` at zero and the negative integers).
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * trigamma(1); // 1.6449340668482264 (pi^2 / 6)
287
+ * ```
288
+ */
289
+ export function trigamma(x, options) {
290
+ return polygamma(1, x, options?.rCompatible ?? true);
291
+ }
292
+ /**
293
+ * Polygamma function, the derivative of order `deriv` of `digamma`. Equivalent to R's
294
+ * `psigamma(x, deriv)` for a single number.
295
+ *
296
+ * `psigamma(-Infinity, deriv)` is `Infinity` for odd `deriv` as in R, which treats it like the poles
297
+ * (`NaN` with `rCompatible: false`). Unlike R, which returns `NaN` for `x < 0` with `deriv >= 6` and
298
+ * where the result overflows, the value is computed there.
299
+ *
300
+ * @param x - Argument.
301
+ * @param deriv - Order of the derivative, an integer from 0 to 100 (rounded half to even; default 0, digamma).
302
+ * @param options - `rCompatible` (default `true`).
303
+ * @returns psi^(deriv)(x). At zero and the negative integers, `Infinity` for odd `deriv` and `NaN` for even.
304
+ * `NaN` if `deriv` is outside 0 to 100.
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * psigamma(2, 2); // -0.40411380631918853 (-2 (zeta(3) - 1))
309
+ * ```
310
+ */
311
+ export function psigamma(x, deriv = 0, options) {
312
+ const n = roundHalfEven(deriv);
313
+ if (Number.isNaN(n) || n < 0 || n > 100)
314
+ return NaN;
315
+ if (n === 0)
316
+ return digamma(x);
317
+ return polygamma(n, x, options?.rCompatible ?? true);
318
+ }