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).
Summary
DfpMath.splitPow(Dfp[], int)andDfpMath.pow(Dfp, int)exponentiate by successivesquaring using an
int trialcounter that is doubled (trial *= 2). For any exponenta ≥ 2^30, the next doubling (2^31) overflowsintand wraps negative, so the loop'stermination test (
trial > ainsplitPow,a > trialinpow) is never satisfied.The loop then spins forever, allocating without bound via
splitMult.Impact
DfpMath.exp(Dfp)guards only|intValue| > 2147483646before delegating tosplitPow,so any argument whose integer part lands in
[2^30, 2147483646]hangs. This is reachablefrom
exp,pow(Dfp,int), andsinh/cosh/tanh/pow(Dfp,Dfp)(which route throughexp). The calling thread hangs at 100% CPU indefinitely (a blocking-GC storm).Affected versions
Current
main/develop. Inherited from Apache Commons Math (also present incommons-math 3.6.1
org.apache.commons.math3.dfp.DfpMath).Reproduction
Root cause
The doubling counter is
int; oncetrialreaches 2^30 the nexttrial *= 2overflowsto a negative value, which is never
> a, so the inner loop never breaks.Fix
Widen
trial/prevtrialtolongin both methods.ais anint(≤ 2^31−1), so alongcounter can always exceed it;a -= trialkeeps compiling (compound assignmentnarrows implicitly, and is exact since
trial == prevtrial ≤ a). No change toexp's2147483646 guard is needed — with the overflow gone,
splitPowruns in O(log a)squarings, and
splitMultalready short-circuits once a factor becomes INFINITE, so hugeexponents collapse to ±Infinity within a few squarings.
Test
Verified after the fix (DfpField(20)): the three repro calls return
Infinity/Infinity/-Infinityin <2 ms;exp(2147483646)=Infinity; ordinary values unchanged(
exp(1)=2.7182818284590452,sinh(2)=3.6268604078470186).