-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8355719: Reduce memory consumption of BigInteger.pow() #24690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 58 commits
98a5b53
926970e
a3f1489
d280c37
91c3d1a
1cfb775
c25bd32
9c32ce4
7ff919b
1f5a9b4
b6c3320
5d971fa
e459c23
48650de
54ec8f8
3ea5190
6c9b364
3ca7d29
4516d88
b427091
524f195
21fbf27
e11b32f
b527fa2
8de6b82
00365c9
100d0e1
1942fd1
0e1a99e
9cd136c
c3bd1b2
f0d0605
f20d19b
8676af7
3cf820b
23914e8
bf099e4
f9bfd22
7ceae87
cb61ddc
10e122e
b94ca7e
139735d
fcd5d55
b831d01
51272bc
70da95c
d85a634
9a5d696
6033d25
5deb21a
280859b
925806b
ad49b56
6895926
b8ca4fe
4103e49
5ebc16b
e0816d5
009937b
2e08f77
261dd31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1246,6 +1246,16 @@ else if (val < 0 && val >= -MAX_CONSTANT) | |||||||||||||
return new BigInteger(val); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Constructs a BigInteger with magnitude specified by the long, | ||||||||||||||
* which may not be zero, and the signum specified by the int. | ||||||||||||||
*/ | ||||||||||||||
private BigInteger(long mag, int signum) { | ||||||||||||||
assert mag != 0 && signum != 0; | ||||||||||||||
this.signum = signum; | ||||||||||||||
this.mag = toMagArray(mag); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Constructs a BigInteger with the specified value, which may not be zero. | ||||||||||||||
*/ | ||||||||||||||
|
@@ -1256,16 +1266,14 @@ private BigInteger(long val) { | |||||||||||||
} else { | ||||||||||||||
signum = 1; | ||||||||||||||
} | ||||||||||||||
mag = toMagArray(val); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
int highWord = (int)(val >>> 32); | ||||||||||||||
if (highWord == 0) { | ||||||||||||||
mag = new int[1]; | ||||||||||||||
mag[0] = (int)val; | ||||||||||||||
} else { | ||||||||||||||
mag = new int[2]; | ||||||||||||||
mag[0] = highWord; | ||||||||||||||
mag[1] = (int)val; | ||||||||||||||
} | ||||||||||||||
private static int[] toMagArray(long mag) { | ||||||||||||||
int highWord = (int) (mag >>> 32); | ||||||||||||||
return highWord == 0 | ||||||||||||||
? new int[] { (int) mag } | ||||||||||||||
: new int[] { highWord, (int) mag }; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
|
@@ -2589,116 +2597,99 @@ public BigInteger pow(int exponent) { | |||||||||||||
if (exponent < 0) { | ||||||||||||||
throw new ArithmeticException("Negative exponent"); | ||||||||||||||
} | ||||||||||||||
if (signum == 0) { | ||||||||||||||
return (exponent == 0 ? ONE : this); | ||||||||||||||
} | ||||||||||||||
if (exponent == 0 || this.equals(ONE)) | ||||||||||||||
return ONE; | ||||||||||||||
|
||||||||||||||
BigInteger partToSquare = this.abs(); | ||||||||||||||
if (signum == 0 || exponent == 1) | ||||||||||||||
return this; | ||||||||||||||
|
||||||||||||||
BigInteger base = this.abs(); | ||||||||||||||
final boolean negative = signum < 0 && (exponent & 1) == 1; | ||||||||||||||
|
||||||||||||||
// Factor out powers of two from the base, as the exponentiation of | ||||||||||||||
// these can be done by left shifts only. | ||||||||||||||
// The remaining part can then be exponentiated faster. The | ||||||||||||||
// powers of two will be multiplied back at the end. | ||||||||||||||
int powersOfTwo = partToSquare.getLowestSetBit(); | ||||||||||||||
int powersOfTwo = base.getLowestSetBit(); | ||||||||||||||
long bitsToShiftLong = (long)powersOfTwo * exponent; | ||||||||||||||
if (bitsToShiftLong > Integer.MAX_VALUE) { | ||||||||||||||
reportOverflow(); | ||||||||||||||
} | ||||||||||||||
int bitsToShift = (int)bitsToShiftLong; | ||||||||||||||
|
||||||||||||||
int remainingBits; | ||||||||||||||
|
||||||||||||||
// Factor the powers of two out quickly by shifting right, if needed. | ||||||||||||||
if (powersOfTwo > 0) { | ||||||||||||||
partToSquare = partToSquare.shiftRight(powersOfTwo); | ||||||||||||||
remainingBits = partToSquare.bitLength(); | ||||||||||||||
if (remainingBits == 1) { // Nothing left but +/- 1? | ||||||||||||||
if (signum < 0 && (exponent&1) == 1) { | ||||||||||||||
return NEGATIVE_ONE.shiftLeft(bitsToShift); | ||||||||||||||
} else { | ||||||||||||||
return ONE.shiftLeft(bitsToShift); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} else { | ||||||||||||||
remainingBits = partToSquare.bitLength(); | ||||||||||||||
if (remainingBits == 1) { // Nothing left but +/- 1? | ||||||||||||||
if (signum < 0 && (exponent&1) == 1) { | ||||||||||||||
return NEGATIVE_ONE; | ||||||||||||||
} else { | ||||||||||||||
return ONE; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
// Factor the powers of two out quickly by shifting right. | ||||||||||||||
base = base.shiftRight(powersOfTwo); | ||||||||||||||
int remainingBits = base.bitLength(); | ||||||||||||||
if (remainingBits == 1) // Nothing left but +/- 1? | ||||||||||||||
return (negative ? NEGATIVE_ONE : ONE).shiftLeft(bitsToShift); | ||||||||||||||
|
||||||||||||||
// This is a quick way to approximate the size of the result, | ||||||||||||||
// similar to doing log2[n] * exponent. This will give an upper bound | ||||||||||||||
// of how big the result can be, and which algorithm to use. | ||||||||||||||
long scaleFactor = (long)remainingBits * exponent; | ||||||||||||||
|
||||||||||||||
// Use slightly different algorithms for small and large operands. | ||||||||||||||
// See if the result will safely fit into a long. (Largest 2^63-1) | ||||||||||||||
if (partToSquare.mag.length == 1 && scaleFactor <= 62) { | ||||||||||||||
// Small number algorithm. Everything fits into a long. | ||||||||||||||
int newSign = (signum <0 && (exponent&1) == 1 ? -1 : 1); | ||||||||||||||
long result = 1; | ||||||||||||||
long baseToPow2 = partToSquare.mag[0] & LONG_MASK; | ||||||||||||||
|
||||||||||||||
int workingExponent = exponent; | ||||||||||||||
|
||||||||||||||
// Perform exponentiation using repeated squaring trick | ||||||||||||||
while (workingExponent != 0) { | ||||||||||||||
if ((workingExponent & 1) == 1) { | ||||||||||||||
result = result * baseToPow2; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if ((workingExponent >>>= 1) != 0) { | ||||||||||||||
baseToPow2 = baseToPow2 * baseToPow2; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
// See if the result will safely fit into an unsigned long. (Largest 2^64-1) | ||||||||||||||
if (scaleFactor <= Long.SIZE) { | ||||||||||||||
// Small number algorithm. Everything fits into an unsigned long. | ||||||||||||||
final int newSign = negative ? -1 : 1; | ||||||||||||||
final long result = unsignedLongPow(base.mag[0] & LONG_MASK, exponent); | ||||||||||||||
|
||||||||||||||
// Multiply back the powers of two (quickly, by shifting left) | ||||||||||||||
if (powersOfTwo > 0) { | ||||||||||||||
if (bitsToShift + scaleFactor <= 62) { // Fits in long? | ||||||||||||||
return valueOf((result << bitsToShift) * newSign); | ||||||||||||||
} else { | ||||||||||||||
return valueOf(result*newSign).shiftLeft(bitsToShift); | ||||||||||||||
} | ||||||||||||||
} else { | ||||||||||||||
return valueOf(result*newSign); | ||||||||||||||
} | ||||||||||||||
return bitsToShift + scaleFactor <= Long.SIZE // Fits in long? | ||||||||||||||
? new BigInteger(result << bitsToShift, newSign) | ||||||||||||||
: new BigInteger(result, newSign).shiftLeft(bitsToShift); | ||||||||||||||
} else { | ||||||||||||||
if ((long)bitLength() * exponent / Integer.SIZE > MAX_MAG_LENGTH) { | ||||||||||||||
if ((bitLength() - 1L) * exponent >= (long) MAX_MAG_LENGTH << 5) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
or
Suggested change
Both variant are easier to read, more honest, and exactly as efficient as with the shift. The right-hand sides are compile-time constants, so they have no impact on runtime performance. More generally, the runtime compilers are perfectly capable to optimize multiplications by constant powers of 2 and replace them with shifts, even if the other operand is not a constant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rgiulietti What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right, but you probably want
Suggested change
I mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, the sufficient condition to get the overflow is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At some point you proposed
Given the value of
that is, to
What am I missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The condition There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. But then your original expression
was a bit too restrictive as well, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On the contrary, it was too loose, as it admitted a bit length equal to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, if B strictly implies A, then B is more restrictive (stronger). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Exactly. |
||||||||||||||
reportOverflow(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Large number algorithm. This is basically identical to | ||||||||||||||
// the algorithm above, but calls multiply() and square() | ||||||||||||||
// the algorithm above, but calls multiply() | ||||||||||||||
// which may use more efficient algorithms for large numbers. | ||||||||||||||
BigInteger answer = ONE; | ||||||||||||||
|
||||||||||||||
int workingExponent = exponent; | ||||||||||||||
final int expZeros = Integer.numberOfLeadingZeros(exponent); | ||||||||||||||
int workingExp = exponent << expZeros; | ||||||||||||||
// Perform exponentiation using repeated squaring trick | ||||||||||||||
while (workingExponent != 0) { | ||||||||||||||
if ((workingExponent & 1) == 1) { | ||||||||||||||
answer = answer.multiply(partToSquare); | ||||||||||||||
} | ||||||||||||||
for (int expLen = Integer.SIZE - expZeros; expLen > 0; expLen--) { | ||||||||||||||
answer = answer.multiply(answer); | ||||||||||||||
fabioromano1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
if (workingExp < 0) // leading bit is set | ||||||||||||||
answer = answer.multiply(base); | ||||||||||||||
|
||||||||||||||
if ((workingExponent >>>= 1) != 0) { | ||||||||||||||
partToSquare = partToSquare.square(); | ||||||||||||||
} | ||||||||||||||
workingExp <<= 1; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Multiply back the (exponentiated) powers of two (quickly, | ||||||||||||||
// by shifting left) | ||||||||||||||
if (powersOfTwo > 0) { | ||||||||||||||
answer = answer.shiftLeft(bitsToShift); | ||||||||||||||
} | ||||||||||||||
answer = answer.shiftLeft(bitsToShift); | ||||||||||||||
return negative ? answer.negate() : answer; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (signum < 0 && (exponent&1) == 1) { | ||||||||||||||
return answer.negate(); | ||||||||||||||
} else { | ||||||||||||||
return answer; | ||||||||||||||
} | ||||||||||||||
/** | ||||||||||||||
* Computes {@code x^n} using repeated squaring trick. | ||||||||||||||
* Assumes {@code x != 0 && x^n < 2^Long.SIZE}. | ||||||||||||||
*/ | ||||||||||||||
static long unsignedLongPow(long x, int n) { | ||||||||||||||
if (x == 1L || n == 0) | ||||||||||||||
return 1L; | ||||||||||||||
|
||||||||||||||
if (x == 2L) | ||||||||||||||
return 1L << n; | ||||||||||||||
|
||||||||||||||
/* | ||||||||||||||
* The method assumption means that n <= 40 here. | ||||||||||||||
* Thus, the loop body executes at most 5 times. | ||||||||||||||
*/ | ||||||||||||||
long pow = 1L; | ||||||||||||||
for (; n != 1; n >>>= 1) { | ||||||||||||||
if ((n & 1) != 0) | ||||||||||||||
pow *= x; | ||||||||||||||
|
||||||||||||||
x *= x; | ||||||||||||||
} | ||||||||||||||
return pow * x; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
|
Uh oh!
There was an error while loading. Please reload this page.