Skip to content

Upper incomplete gamma for s < -1 at small x; exact square roots of negative integers over integers - #447

Closed
enumeratio wants to merge 1 commit into
cortex-js:mainfrom
enumeratio:numerics-0154
Closed

enumeratio wants to merge 1 commit into
cortex-js:mainfrom
enumeratio:numerics-0154

Conversation

@enumeratio

Copy link
Copy Markdown
Contributor

Fix #446.

Gamma(s, x) for a non-integer s < -1 and x < 1. The Legendre continued fraction does not converge in its cap there, and the error grew as x shrank: Gamma(-1.5, 0.001) was 21020.691761… where mpmath has 21020.937167… (1.2e-5), and Gamma(-1.5, 1e-6) was off by 4.2e-5. We now start from Γ(s+n, x) with -1 < s+n < 0 and apply Γ(a-1, x) = (Γ(a, x) - x^{a-1}e^{-x})/(a-1) (DLMF 8.8.2) n times; at x < 1 the subtracted term dominates, so nothing cancels. Within 0.05 of a pole of Γ(s+n) the base Γ(a) - γ(a, x) cancels, so the real kernel defers to the complex kernel there. Measured against mpmath, the relative error is below 3e-15 on s in {-0.5, -1.5, -2.5, -3.5} by x in {1e-6, 1e-3, 1e-2, 0.1, 1}, and below 5e-14 on s = -(k+f) with k up to 12, 0.001 <= f <= 0.999 and 1e-10 <= x < 1. There is no bignum kernel for this head, so ce.precision does not change the result.

The compiled JavaScript lane had a separate bug: Gamma(x, y) compiled to _SYS.gamma(x) and dropped y (Gamma(-1.5, 0.001) ran as 2.3633). It now calls the upper incomplete gamma, with the complex kernel for a complex operand. The interval target declines the two-operand form, as the GPU targets do.

Sqrt(-13)/3 and its relatives. A negative integer radical went to the float lane, which took Math.sqrt(-13). Sqrt(-13)/3, Sqrt(-2)/3, Sqrt(-13)/2, Sqrt(-13)/3.0 and Sqrt(-13)*Rational(1,3) were NaN, and so was Arcosh(Sqrt(-13)/3). _numericValue now builds the exact pure-imaginary value √c·i for a radical -c, so canonicalization, evaluate() and .N() agree: Sqrt(-13)/3 is (√13/3)i and Arcosh(Sqrt(-13)/3) is 1.0171572086841316 + 1.5707963267948966i. While there, Sqrt(-13)/Sqrt(-3) was -√39/3; it is √39/3.

…ith DLMF 8.8.2, and the compiled JavaScript Gamma(s, z) that dropped z; build exact pure-imaginary values for negative integer radicals so Sqrt(-13)/3 is not NaN. Fix cortex-js#446
enumeratio added a commit to enumeratio/enumeratio that referenced this pull request Oct 16, 2026
Every engine we ship or evaluate examples on now sets compute-engine's `ce.roundingTies = "to-even"` (option since 0.149, cortex-js/compute-engine#417), so `Round(5/2)` is 2 and `Round(-2.5)` is -2, as in Wolfram, SymPy, mpmath, Sage and Julia. CE's own default is untouched.

## Where it is set

One function, `setEngineDefaults(ce)` in `@enumeratio/engine`, called from:

- `declareEvaluation` (the base library): covers every worker, session/kernel engine, and every planned engine (census `fullEngine`, reference `declaredEngine` and the isolated evaluator's `configure`)
- the frontend engine (lazy import, so the page chunk stays engine-free), the CLI `Session`, the site prerender engine, the forms collector
- `createEngine` / `createLatexEngine` in `@enumeratio/engine/testing` (not `bareEngine`)

Bare engines that snapshot CE's own bindings (census/manifest scripts, ce-patches landed checks, oracle comparison engines) are left as they are. The CLI and web packages gain a workspace dependency on `@enumeratio/engine`.

## Changed rows (Round)

Each is a tie; the new value is Wolfram's. Three ids that said the opposite are renamed (no alias): `rounds-half-away-from-zero-not-banker-s-rounding` to `rounds-half-to-even`, `and-by-the-same-convention-2-5-rounds-to-3-not-2` to `minus-2-5-rounds-to-minus-2`, `an-exact-half-rounds-away-from-zero-as-well` to `an-exact-half-rounds-to-even`; the sidecars moved with them. Rows below are listed by their new ids.

| row | old | new |
| --- | --- | --- |
| `round-neg-1-over-2-exact-tie` | -1 | 0 |
| `rounds-half-to-even` | 3 | 2 |
| `minus-2-5-rounds-to-minus-2` | -3 | -2 |
| `round-5-over-2-exact-rational-tie` | 3 | 2 |
| `an-exact-half-rounds-to-even` | 1 | 0 |

Captions and `index.md` prose now say ties go to even. The `rounding-mode` classification is dropped for Wolfram, Sage, SymPy, mpmath and Julia, whose rows now agree (rescanned: wolfram, sage, sympy, mpmath, julia). Rust's `round` goes away from zero, so those rows are now classified `rounding-mode` for Rust only. No other `known` was touched. The other Round rows (7/2, 3.5, the list row, 19.995 at step 1/100, ...) are unchanged. No other examples.tsv row depends on tie rounding.

Also updated: the Ceil and Floor notes, and the site's Wolfram source-output row for `Round(2.5)`, which claimed a divergence.

## Two-argument Gamma compile

`Gamma(s, x)` with a symbolic `x` compiled (JS target) to `_SYS.gamma(2, _.x + 1)`, which drops x (cortex-js/compute-engine#447). `declareAnalytic` now lets `Gamma` compile with one operand only, so the two-operand call falls back to the interpreter; a test checks compiled `Gamma` and `GammaRegularized` either decline or match evaluate. `GammaRegularized` compiled correctly and is unchanged. Lift the guard (the `1` in the `Gamma` row of the loop in `declare.ts`) with the CE bump that carries #447. Nothing else in the repo lowers two-argument Gamma (ce-patches WGSL, frontend `complex-eval`, the combinatorics compile scripts and the bench catalogue use only the one-argument form).
@arnog

arnog commented Oct 17, 2026

Copy link
Copy Markdown
Member

Landed on main as 38e5d18 with the dual-review fixes: the shift loop is capped at order 4096 and returns Infinity on overflow; two-operand Gamma is a complex-continuation head so the compiled lane agrees with .N() for a real z < 0; 1 − eᵗ is recognized by the new exponential-divisor pole family. Thank you.

@arnog arnog closed this Oct 17, 2026
arnog added a commit that referenced this pull request Oct 17, 2026
…al-divisor poles

Lands PR #447 by enumeratio with review fixes, plus one maintainer fix found
while reviewing PR #445.

- Gamma(s, z) for a non-integer s < −1 and z < 1: start from Γ(s+n, z) with
  −1 < s+n < 0 and apply DLMF 8.8.2 n times (the Legendre fraction did not
  converge there; Gamma(-1.5, 0.001) was off by 1.2e-5). The shift is capped
  at order 4096 and an overflow returns Infinity. Measured against mpmath:
  relative error below 4e-14 over 132 points.
- Compiled Gamma(s, z) keeps its second operand (it compiled as Γ(s)), and is
  a complex-continuation head: a real z < 0 takes the complex kernel unless s
  is a positive integer, as .N() does. The interval target declines the
  two-operand form.
- Sqrt(-13)/3 is (√13/3)i, not NaN: a negative integer radical builds the
  exact pure-imaginary value in _numericValue. Sqrt(-13)/Sqrt(-3) is √39/3,
  not −√39/3.
- Interior-pole detection knows an exponential divisor k·bᵘ + c with u linear
  (eˣ − 1, 1 − eˣ, 2ᵗ − 8): ∫₋₁¹ eˣ/(eˣ − 1) dx is Indeterminate, where it
  evaluated to 1. The free-symbol scan still lets an assumption decide
  1/(eᵗ − a).

New ROADMAP entry: an integer coefficient times √(−c) stays a product, and
√(−c) with c ≥ 10⁶ keeps its square factors (correct, unreduced).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Gamma(s, x) loses 5 digits for s < −1 and small x, and compiles without x; Sqrt(-13)/3 is NaN

2 participants