( f: (x: number) => number, x: number, dir = 1, deadline?: number )
| 362 | * - catastrophic cancellation: the magnitude grows to an interior peak and |
| 363 | * then collapses to ~0. |
| 364 | * |
| 365 | * Beyond that horizon the samples are floating-point garbage. Left unchecked, |
| 366 | * a collapse to a run of identical `0`s makes `extrapolate` report `err = 0` |
| 367 | * (perfect "convergence") and return a spurious value — e.g. |
| 368 | * `lim_{x→∞} (e^{x·e^{−x}/…} − eˣ)/x = −e²`, whose two `eˣ` terms cancel to 0 |
| 369 | * around x ≈ 40 and overflow to `NaN` past x ≈ 710, yielding a wrong `0`. |
| 370 | * Capping `extrapolate`'s `maxeval` to the clean prefix makes it report |
| 371 | * non-convergence instead, so `limit()` returns `NaN` (not-evaluable). |
| 372 | * |
| 373 | * The schedule here must mirror `extrapolate()` (default `contract = 0.125`, |
| 374 | * and the `x = 1/u` change of variables for an infinite target). |
| 375 | */ |
| 376 | function reliableLimitSamples( |
| 377 | f: (x: number) => number, |
| 378 | x0: number, |
| 379 | step: number, |
| 380 | deadline?: number |
| 381 | ): number { |
| 382 | const CONTRACT = 0.125; // must match extrapolate()'s default contract |
| 383 | const MAX = 60; |
| 384 | const inf = !Number.isFinite(x0); |
| 385 | // The actual argument passed to `f` for a given step `h` (with the `x = 1/u` |
| 386 | // change of variables for an infinite target, matching extrapolate()). |
| 387 | const arg = (h: number) => (inf ? 1 / h : x0 + h); |
| 388 | |
| 389 | // A run of (nearly) identical samples can be a real limit, or a |
| 390 | // floating-point artifact: a function whose numerically-meaningful window is |
| 391 | // narrower than the ladder spacing reads as a constant because every ladder |
| 392 | // point lands in an overflow/underflow region (e.g. a denominator with a |
| 393 | // triple exponential overflows for all x ≳ 2, so f ≈ 0 at x = 1, 8, 64 … |
| 394 | // while the true value lives near x ≈ 1.5). Corroborate by probing a few |
| 395 | // points *between* the two ladder steps; the settle is real only if they all |
| 396 | // match. |
| 397 | const settleIsReal = (hA: number, hB: number, v: number): boolean => { |
| 398 | // A generous tolerance: we are separating real fp noise (a converging |
| 399 | // sequence such as (1+1/x)^x is noisy to ~x·ε near its limit) from a |
| 400 | // genuinely skipped window (an overflow artifact's true value differs by an |
no test coverage detected