Skip to content

Stop ExpM1, LogP1 and the hyperbolics building million-digit sums for large arguments [patch] - #168

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/133-bounded-sums-large-arguments
Oct 9, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/133-bounded-sums-large-arguments

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #133

What was wrong

Add and Subtract are exact, so they align both operands to the smaller exponent. When one operand is millions of digits below the other, that builds a significand millions of digits wide, and the caller then rounds almost all of it away:

  • ExpM1(-1e7) took about 10 s to return -1.
  • ExpM1(-1e10) threw OverflowException, because Exp(-1e10) overflows its own exponent.
  • LogP1, Sinh, Cosh, Asinh and Acosh took tens of seconds on arguments like 1e7 and 1e1000000.

Change

This is the general fix the triage comment suggested.

  • New helpers. AddToPrecision and SubtractToPrecision (internal, in PreciseNumber.cs) check whether the smaller operand lies wholly below both the larger operand's last digit and the caller's rounding position (with 3 guard digits). If it does, the smaller operand is replaced by one unit of its sign at that position. The exact sum and this sticky sum fall strictly inside the same gap between neighbouring multiples of that digit. Every rounding boundary at the caller's precision is a multiple of it, including after the halving in Sinh/Cosh, so both sums round the same way.

  • Where they're used.

    • ExpM1: the - 1.
    • LogP1: the 1 +.
    • Sinh/Cosh: the ± 1/e^x.
    • Asinh: 1 + x² and 1 + √….
    • Acosh: x ∓ 1.

    Near one these sums are still exact, because the helper only takes over when the gap exceeds the working width. The cancellation-sensitive small-argument paths described in CLAUDE.md are unchanged.

  • Early return in ExpM1. It returns -1 once x < -3·(working + 1). Since ln 10 < 3, e^x there is below every kept digit. This covers Exp2M1 and Exp10M1 too, because they route through ExpM1.

  • Docs. CLAUDE.md now describes the helpers and the new test file.

Tests

Added PreciseNumberLargeArgumentTests:

  • ExpM1, Exp2M1 and Exp10M1 of -1e10 return -1.
  • ExpM1(±…), Exp10M1(-1e7), LogP1(1e1000000), Sinh(±1e7), Cosh(-1e7), Asinh(±1e1000000) and Acosh(1e1000000) each finish within a 5 s budget. They run on the thread pool so the test fails fast instead of hanging. The test also checks they equal Exp(x), Log(x), e^x/2 and ln(2x) respectively.
  • 50-digit results for ExpM1(-100), ExpM1(300), Exp10M1(100.5), LogP1(3.7e300), Sinh(300), Cosh(-300.5), Asinh(-2.5e300), Acosh(7.25e300) and Acosh(1e40) are pinned. These values were captured from the exact path before this change, and the bounded path reproduces every one.

How the tests were checked:

  • With the library change reverted: 5 of the 6 new tests fail. One fails with the OverflowException; the other four overrun the 5 s budget. The digit-pinning test passes on both versions, which is what it exists to show.
  • Full suite: 475/475 pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTeaDNAaTEviJbNWuEi5hP


Generated by Claude Code

… large arguments [patch]

Exact addition aligns both operands to the smaller exponent, so subtracting
one from e^-1e7, or adding one to 1e1000000, built a significand millions of
digits wide only for the caller to round it away. ExpM1(-1e10) overflowed
the exponent of e^-1e10 before it got that far.

AddToPrecision and SubtractToPrecision collapse an operand that lies wholly
below the larger one's last digit and the caller's rounding position into a
single sticky unit, which rounds the same way as the exact sum. ExpM1,
LogP1, Sinh, Cosh, Asinh and Acosh use them, and ExpM1 returns -1 outright
once e^x is below the working width.

Fixes #133

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTeaDNAaTEviJbNWuEi5hP
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit e4c291c into main Oct 9, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/133-bounded-sums-large-arguments branch October 9, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExpM1(-1e10) throws OverflowException instead of returning -1, and ExpM1/LogP1/Sinh/Cosh/Asinh/Acosh take seconds to minutes for large arguments

2 participants