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

@AviranAbady

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions