(ce: ComputeEngine, z: BigNum)
| 827 | const table = computeBernoulliEven(buildTerms, ce._deadline); |
| 828 | bernoulliCache.set(ce, table); |
| 829 | |
| 830 | // Return the full cached table (length ≥ minTerms), NOT a `minTerms` slice. |
| 831 | // Every caller already clamps its own consumption — the four asymptotic |
| 832 | // series (gammaln/digamma/trigamma/polygamma) iterate to |
| 833 | // `Math.min(maxTerms, table.length)` and break at the tolerance well before |
| 834 | // that, and `zetaCore` indexes a single fixed element `table[k-1]` — so no |
| 835 | // caller ever over-sums past its requested `minTerms`. The asymptotic-series |
| 836 | // divergence trap (extra terms make the sum worse) is therefore handled at |
| 837 | // the caller's clamp, and the per-call `slice` copy is pure overhead. (The |
| 838 | // clamp, not the table length, is what enforces the asymptotic cutoff.) |
| 839 | return table; |
| 840 | } |
| 841 | |
| 842 | /** |
| 843 | * Bignum Digamma function ψ(z) = d/dz ln(Γ(z)) |
| 844 | * Same algorithm as machine `digamma`: reflection for negative z, |
| 845 | * recurrence to shift z > 7, then asymptotic expansion with Bernoulli numbers. |
| 846 | */ |
| 847 | export function bigDigamma(ce: ComputeEngine, z: BigNum): BigNum { |
| 848 | if (!z.isFinite()) return BigDecimal.NAN; |
| 849 | return withGuardDigits(SPECIAL_FN_GUARD, () => digammaCore(ce, z)); |
| 850 | } |
| 851 | |
| 852 | function digammaCore(ce: ComputeEngine, z: BigNum): BigNum { |
| 853 | // Reflection formula for negative values: ψ(1-z) = ψ(z) + π·cot(πz) |
| 854 | if (z.isNegative()) { |
| 855 | if (z.isInteger()) return BigDecimal.NAN; // poles at non-positive integers |
| 856 | const pi = BigDecimal.PI; |
| 857 | const piZ = pi.mul(z); |
| 858 | const cotPiZ = piZ.cos().div(piZ.sin()); |
| 859 | return digammaCore(ce, BigDecimal.ONE.sub(z)).sub(pi.mul(cotPiZ)); |
| 860 | } |
| 861 | |
| 862 | if (z.isZero()) return BigDecimal.NAN; // pole |
| 863 | |
| 864 | const p = BigDecimal.precision; |
| 865 | const guard = 10; |
| 866 | // Shift to w ≈ p (not the old 0.37·p, which left the asymptotic series' floor |
| 867 | // at the target tolerance, forcing it to run its full ≈π·w terms). The larger |
| 868 | // shift converges the series in ≈0.4·p terms; see the note in `gammalnCore`. |
| 869 | const shift = Math.max(7, Math.ceil(p)); |
| 870 | |
| 871 | // Recurrence: ψ(z+1) = ψ(z) + 1/z — shift z up until z > shift |
| 872 | let result = new BigDecimal(0); |
| 873 | let w = z; |
| 874 | while (w.lt(shift)) { |
| 875 | result = result.sub(BigDecimal.ONE.div(w)); |
| 876 | w = w.add(BigDecimal.ONE); |
| 877 | } |
| 878 | |
| 879 | const maxTerms = Math.max(20, Math.ceil(0.6 * p) + 20); |
| 880 | const bernoulli = getBernoulliRationals(ce, maxTerms); |
| 881 | |
| 882 | // Asymptotic expansion: ψ(w) ~ ln(w) - 1/(2w) - Σ B_{2k}/(2k·w^{2k}). |
| 883 | // Round the running power w^{2k} each step: `mul` keeps the full product, so an |
no test coverage detected