Skip to content

DfpMath.splitPow / pow(Dfp,int): int overflow makes exponents ≥ 2^30 loop forever (exp/sinh/cosh/tanh of a large argument hangs) #465

Description

@AviranAbady

Summary

DfpMath.splitPow(Dfp[], int) and DfpMath.pow(Dfp, int) exponentiate by successive
squaring using an int trial counter that is doubled (trial *= 2). For any exponent
a ≥ 2^30, the next doubling (2^31) overflows int and wraps negative, so the loop's
termination test (trial > a in splitPow, a > trial in pow) is never satisfied.
The loop then spins forever, allocating without bound via splitMult.

Impact

DfpMath.exp(Dfp) guards only |intValue| > 2147483646 before delegating to splitPow,
so any argument whose integer part lands in [2^30, 2147483646] hangs. This is reachable
from exp, pow(Dfp,int), and sinh/cosh/tanh/pow(Dfp,Dfp) (which route through
exp). The calling thread hangs at 100% CPU indefinitely (a blocking-GC storm).

Affected versions

Current main/develop. Inherited from Apache Commons Math (also present in
commons-math 3.6.1 org.apache.commons.math3.dfp.DfpMath).

Reproduction

DfpField field = new DfpField(20);
DfpMath.pow(field.newDfp("3"), 1 << 30);   // 3^(2^30)     -> hangs
DfpMath.exp(field.newDfp("2000000000"));    // e^2e9        -> hangs
DfpMath.sinh(field.newDfp("-1500000000"));  // sinh(-1.5e9) -> hangs

Root cause

The doubling counter is int; once trial reaches 2^30 the next trial *= 2 overflows
to a negative value, which is never > a, so the inner loop never breaks.

Fix

Widen trial/prevtrial to long in both methods. a is an int (≤ 2^31−1), so a
long counter can always exceed it; a -= trial keeps compiling (compound assignment
narrows implicitly, and is exact since trial == prevtrial ≤ a). No change to exp's
2147483646 guard is needed — with the overflow gone, splitPow runs in O(log a)
squarings, and splitMult already short-circuits once a factor becomes INFINITE, so huge
exponents collapse to ±Infinity within a few squarings.

--- a/hipparchus-core/src/main/java/org/hipparchus/dfp/DfpMath.java
+++ b/hipparchus-core/src/main/java/org/hipparchus/dfp/DfpMath.java
@@ splitPow(Dfp[] base, int a) @@
             r[0] = new Dfp(base[0]);
             r[1] = new Dfp(base[1]);
-            int trial = 1;
+            long trial = 1;

-            int prevtrial;
+            long prevtrial;
             while (true) {
                 prevtrial = trial;
                 trial *= 2;
                 if (trial > a) {
                     break;
                 }
                 r = splitMult(r, r);
             }
             Dfp r = new Dfp(base);
             Dfp prevr;
-            int trial = 1;
-            int prevtrial;
+            long trial = 1;
+            long prevtrial;

             do {
                 prevr = new Dfp(r);
                 prevtrial = trial;
                 r = r.square();
                 trial *= 2;
             } while (a>trial);

Test

@Test
@Timeout(10) // fails (hangs) without the fix
void testLargeIntegerExponentDoesNotHang() {
    final DfpField field = new DfpField(20);
    Assertions.assertTrue(DfpMath.pow(field.newDfp("3"), 1 << 30).isInfinite());   // 3^(2^30)
    Assertions.assertTrue(DfpMath.exp(field.newDfp("2000000000")).isInfinite());   // e^2e9
    Assertions.assertTrue(DfpMath.sinh(field.newDfp("-1500000000")).isInfinite()); // -Inf
}

Verified after the fix (DfpField(20)): the three repro calls return Infinity/Infinity/
-Infinity in <2 ms; exp(2147483646)=Infinity; ordinary values unchanged
(exp(1)=2.7182818284590452, sinh(2)=3.6268604078470186).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions