Skip to content

Exp2(7000000000, 3) throws OverflowException while Exp2(7000000000.5, 3) works, and integer Exp2 ignores significantDigits: Exp2(5000000, 10) builds 1.5 million digits in 3.3 s #150

Open

Description

@matt-edmondson

What's wrong

Exp2(x, significantDigits) sends every integer x to Two.Pow(x):

if (IsInteger(x))
{
return Two.Pow(x);
}

Pow's integer path is exact exponentiation by squaring, and it begins with power.Abs().To<int>():

for (int remaining = power.Abs().To<int>(); remaining > 0; remaining >>= 1)

That causes two problems:

  1. It throws for large exponents. For |x| ≥ 2^31, To<int> throws, even though 2^x is easily representable to the requested digits.
  2. It ignores the requested precision. For large |x|, the exact result has about 0.301·|x| digits (0.699·|x| for negative x). All of them are computed and returned regardless of significantDigits.

Every non-integer x takes the exp(x · ln 2) path, which is fast and correctly rounded. So does Exp2M1, even when its argument is an integer.

Reproduction (net10.0, Release)

Call Observed Expected (mpmath)
Exp2(7000000000.5, 3) 6.29e2107209967, 36 ms ✓
Exp2(6999999999.9999999999, 3) 4.44e2107209969, 1 ms ✓
Exp2(7000000000, 3) OverflowException: Value was either too large or too small for an Int32. 4.44e2107209969
Exp2(-6000000000.5, 5) 7.3383e-1806179975, 0 ms ✓
Exp2(-6000000000, 5) OverflowException (same message) 1.0378e-1806179974
Exp2(1000000, 10) 301,030 significant digits, 303 ms, 13.6 MB 10 digits
Exp2(-1000000, 10) 698,971 significant digits, 979 ms 10 digits
Exp2(5000000, 10) 1,505,150 digits, 3.3 s (formatting it takes another ~80 s) 9.513052773e1505149
Exp2(5000000.5, 10) 10 digits, 0 ms ✓
Exp2M1(1000000, 10) 10 digits ✓ (inconsistent with Exp2)

The remark on Exp2 says "An integer power of two is produced by repeated squaring, which is exact". That contradicts the method's own significantDigits parameter. Exp10 already treats an integer argument as a pure exponent shift, and Exp2M1 honours the requested precision.

Why it matters

How fast the call is, and whether it throws, depends on whether x happens to be an integer. For example, Exp2(n, 20) in a loop over n hangs or crashes on values whose non-integer neighbours return instantly. Producing x · log10(2) digits for a call that asked for 3 is a large and unexpected cost.

Related: #128 is the same To<int> limit in Pow(-1, n). #123 (closed) fixed the non-integer path.

Suggested fix and acceptance criteria

  • In Exp2, use the exact Two.Pow(x) only when the exact result fits in significantDigits:
    • positive x: when |x|·log10 2 ≤ significantDigits
    • negative x: when the 0.699·|x| digits of 5^|x| fit
  • Otherwise, use ExpOfProductWithConstant, which the tests already show is correctly rounded at magnitudes like these.
  • Keep Exp2(10) == 1024 and Exp2(-3) == 0.125 exact, as pinned today.
  • Tests:
    • Exp2(7000000000, 3) == 4.44e2107209969
    • Exp2(-6000000000, 5) == 1.0378e-1806179974
    • Exp2(5000000, 10) returns 10 digits in under 100 ms
  • Update the remark on Exp2.

Activity

  1. matt-edmondson commented on Oct 6, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions