8229845: Decrease memory consumption of BigInteger.toString()

Reviewed-by: redestad
This commit is contained in:
Brian Burkhalter 2020-11-20 16:23:41 +00:00
parent ff00c591c3
commit 2ae3e51f59
2 changed files with 94 additions and 58 deletions
src/java.base/share/classes/java/math
test/jdk/java/math/BigInteger

@ -3950,35 +3950,66 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
// If it's small enough, use smallToString.
if (mag.length <= SCHOENHAGE_BASE_CONVERSION_THRESHOLD)
return smallToString(radix);
BigInteger abs = this.abs();
// Ensure buffer capacity sufficient to contain string representation
// floor(bitLength*log(2)/log(radix)) + 1
// plus an additional character for the sign if negative.
int b = abs.bitLength();
int numChars = (int)(Math.floor(b*LOG_TWO/logCache[radix]) + 1) +
(signum < 0 ? 1 : 0);
StringBuilder sb = new StringBuilder(numChars);
// Otherwise use recursive toString, which requires positive arguments.
// The results will be concatenated into this StringBuilder
StringBuilder sb = new StringBuilder();
if (signum < 0) {
toString(this.negate(), sb, radix, 0);
sb.insert(0, '-');
sb.append('-');
}
else
toString(this, sb, radix, 0);
// Use recursive toString.
toString(abs, sb, radix, 0);
return sb.toString();
}
/** This method is used to perform toString when arguments are small. */
private String smallToString(int radix) {
/**
* If {@code numZeros > 0}, appends that many zeros to the
* specified StringBuilder; otherwise, does nothing.
*
* @param sb The StringBuilder that will be appended to.
* @param numZeros The number of zeros to append.
*/
private static void padWithZeros(StringBuilder buf, int numZeros) {
while (numZeros >= NUM_ZEROS) {
buf.append(ZEROS);
numZeros -= NUM_ZEROS;
}
if (numZeros > 0) {
buf.append(ZEROS, 0, numZeros);
}
}
/**
* This method is used to perform toString when arguments are small.
* The value must be non-negative. If {@code digits <= 0} no padding
* (pre-pending with zeros) will be effected.
*
* @param radix The base to convert to.
* @param sb The StringBuilder that will be appended to in place.
* @param digits The minimum number of digits to pad to.
*/
private void smallToString(int radix, StringBuilder buf, int digits) {
assert signum >= 0;
if (signum == 0) {
return "0";
padWithZeros(buf, digits);
return;
}
// Compute upper bound on number of digit groups and allocate space
int maxNumDigitGroups = (4*mag.length + 6)/7;
String digitGroup[] = new String[maxNumDigitGroups];
long[] digitGroups = new long[maxNumDigitGroups];
// Translate number to string, a digit group at a time
BigInteger tmp = this.abs();
BigInteger tmp = this;
int numGroups = 0;
while (tmp.signum != 0) {
BigInteger d = longRadix[radix];
@ -3990,33 +4021,37 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
BigInteger q2 = q.toBigInteger(tmp.signum * d.signum);
BigInteger r2 = r.toBigInteger(tmp.signum * d.signum);
digitGroup[numGroups++] = Long.toString(r2.longValue(), radix);
digitGroups[numGroups++] = r2.longValue();
tmp = q2;
}
// Put sign (if any) and first digit group into result buffer
StringBuilder buf = new StringBuilder(numGroups*digitsPerLong[radix]+1);
if (signum < 0) {
buf.append('-');
}
buf.append(digitGroup[numGroups-1]);
// Get string version of first digit group
String s = Long.toString(digitGroups[numGroups-1], radix);
// Append remaining digit groups padded with leading zeros
// Pad with internal zeros if necessary.
padWithZeros(buf, digits - (s.length() +
(numGroups - 1)*digitsPerLong[radix]));
// Put first digit group into result buffer
buf.append(s);
// Append remaining digit groups each padded with leading zeros
for (int i=numGroups-2; i >= 0; i--) {
// Prepend (any) leading zeros for this digit group
int numLeadingZeros = digitsPerLong[radix]-digitGroup[i].length();
s = Long.toString(digitGroups[i], radix);
int numLeadingZeros = digitsPerLong[radix] - s.length();
if (numLeadingZeros != 0) {
buf.append(zeros[numLeadingZeros]);
buf.append(ZEROS, 0, numLeadingZeros);
}
buf.append(digitGroup[i]);
buf.append(s);
}
return buf.toString();
}
/**
* Converts the specified BigInteger to a string and appends to
* {@code sb}. This implements the recursive Schoenhage algorithm
* for base conversions.
* for base conversions. This method can only be called for non-negative
* numbers.
* <p>
* See Knuth, Donald, _The Art of Computer Programming_, Vol. 2,
* Answers to Exercises (4.4) Question 14.
@ -4026,32 +4061,26 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
* @param radix The base to convert to.
* @param digits The minimum number of digits to pad to.
*/
private static void toString(BigInteger u, StringBuilder sb, int radix,
int digits) {
private static void toString(BigInteger u, StringBuilder sb,
int radix, int digits) {
assert u.signum() >= 0;
// If we're smaller than a certain threshold, use the smallToString
// method, padding with leading zeroes when necessary.
// method, padding with leading zeroes when necessary unless we're
// at the beginning of the string or digits <= 0. As u.signum() >= 0,
// smallToString() will not prepend a negative sign.
if (u.mag.length <= SCHOENHAGE_BASE_CONVERSION_THRESHOLD) {
String s = u.smallToString(radix);
// Pad with internal zeros if necessary.
// Don't pad if we're at the beginning of the string.
if ((s.length() < digits) && (sb.length() > 0)) {
for (int i=s.length(); i < digits; i++) {
sb.append('0');
}
}
sb.append(s);
u.smallToString(radix, sb, digits);
return;
}
int b, n;
b = u.bitLength();
// Calculate a value for n in the equation radix^(2^n) = u
// and subtract 1 from that value. This is used to find the
// cache index that contains the best value to divide u.
n = (int) Math.round(Math.log(b * LOG_TWO / logCache[radix]) / LOG_TWO - 1.0);
int b = u.bitLength();
int n = (int) Math.round(Math.log(b * LOG_TWO / logCache[radix]) /
LOG_TWO - 1.0);
BigInteger v = getRadixConversionCache(radix, n);
BigInteger[] results;
results = u.divideAndRemainder(v);
@ -4059,7 +4088,7 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
int expectedDigits = 1 << n;
// Now recursively build the two halves of each number.
toString(results[0], sb, radix, digits-expectedDigits);
toString(results[0], sb, radix, digits - expectedDigits);
toString(results[1], sb, radix, expectedDigits);
}
@ -4091,14 +4120,11 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
return cacheLine[exponent];
}
/* zero[i] is a string of i consecutive zeros. */
private static String zeros[] = new String[64];
static {
zeros[63] =
"000000000000000000000000000000000000000000000000000000000000000";
for (int i=0; i < 63; i++)
zeros[i] = zeros[63].substring(0, i);
}
/* Size of ZEROS string. */
private static int NUM_ZEROS = 63;
/* ZEROS is a string of NUM_ZEROS consecutive zeros. */
private static final String ZEROS = "0".repeat(NUM_ZEROS);
/**
* Returns the decimal String representation of this BigInteger.

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,7 +26,7 @@
* @library /test/lib
* @build jdk.test.lib.RandomFactory
* @run main BigIntegerTest
* @bug 4181191 4161971 4227146 4194389 4823171 4624738 4812225 4837946 4026465 8074460 8078672 8032027
* @bug 4181191 4161971 4227146 4194389 4823171 4624738 4812225 4837946 4026465 8074460 8078672 8032027 8229845
* @summary tests methods in BigInteger (use -Dseed=X to set PRNG seed)
* @run main/timeout=400 BigIntegerTest
* @author madbot
@ -795,7 +795,7 @@ public class BigIntegerTest {
// Generic string conversion.
for (int i=0; i<100; i++) {
byte xBytes[] = new byte[Math.abs(random.nextInt())%100+1];
byte xBytes[] = new byte[Math.abs(random.nextInt())%200+1];
random.nextBytes(xBytes);
BigInteger x = new BigInteger(xBytes);
@ -836,6 +836,16 @@ public class BigIntegerTest {
}
}
// Check value with many trailing zeros.
String val = "123456789" + "0".repeat(200);
BigInteger b = new BigInteger(val);
String s = b.toString();
if (!val.equals(s)) {
System.err.format("Expected length %d but got %d%n",
val.length(), s.length());
failCount++;
}
report("String Conversion", failCount);
}