Skip to content

Commit 2ae3e51

Browse files
author
Brian Burkhalter
committed
8229845: Decrease memory consumption of BigInteger.toString()
Reviewed-by: redestad
1 parent ff00c59 commit 2ae3e51

File tree

2 files changed

+94
-58
lines changed

2 files changed

+94
-58
lines changed

src/java.base/share/classes/java/math/BigInteger.java

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,35 +3950,66 @@ public String toString(int radix) {
39503950
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
39513951
radix = 10;
39523952

3953-
// If it's small enough, use smallToString.
3954-
if (mag.length <= SCHOENHAGE_BASE_CONVERSION_THRESHOLD)
3955-
return smallToString(radix);
3953+
BigInteger abs = this.abs();
3954+
3955+
// Ensure buffer capacity sufficient to contain string representation
3956+
// floor(bitLength*log(2)/log(radix)) + 1
3957+
// plus an additional character for the sign if negative.
3958+
int b = abs.bitLength();
3959+
int numChars = (int)(Math.floor(b*LOG_TWO/logCache[radix]) + 1) +
3960+
(signum < 0 ? 1 : 0);
3961+
StringBuilder sb = new StringBuilder(numChars);
39563962

3957-
// Otherwise use recursive toString, which requires positive arguments.
3958-
// The results will be concatenated into this StringBuilder
3959-
StringBuilder sb = new StringBuilder();
39603963
if (signum < 0) {
3961-
toString(this.negate(), sb, radix, 0);
3962-
sb.insert(0, '-');
3964+
sb.append('-');
39633965
}
3964-
else
3965-
toString(this, sb, radix, 0);
3966+
3967+
// Use recursive toString.
3968+
toString(abs, sb, radix, 0);
39663969

39673970
return sb.toString();
39683971
}
39693972

3970-
/** This method is used to perform toString when arguments are small. */
3971-
private String smallToString(int radix) {
3973+
/**
3974+
* If {@code numZeros > 0}, appends that many zeros to the
3975+
* specified StringBuilder; otherwise, does nothing.
3976+
*
3977+
* @param sb The StringBuilder that will be appended to.
3978+
* @param numZeros The number of zeros to append.
3979+
*/
3980+
private static void padWithZeros(StringBuilder buf, int numZeros) {
3981+
while (numZeros >= NUM_ZEROS) {
3982+
buf.append(ZEROS);
3983+
numZeros -= NUM_ZEROS;
3984+
}
3985+
if (numZeros > 0) {
3986+
buf.append(ZEROS, 0, numZeros);
3987+
}
3988+
}
3989+
3990+
/**
3991+
* This method is used to perform toString when arguments are small.
3992+
* The value must be non-negative. If {@code digits <= 0} no padding
3993+
* (pre-pending with zeros) will be effected.
3994+
*
3995+
* @param radix The base to convert to.
3996+
* @param sb The StringBuilder that will be appended to in place.
3997+
* @param digits The minimum number of digits to pad to.
3998+
*/
3999+
private void smallToString(int radix, StringBuilder buf, int digits) {
4000+
assert signum >= 0;
4001+
39724002
if (signum == 0) {
3973-
return "0";
4003+
padWithZeros(buf, digits);
4004+
return;
39744005
}
39754006

39764007
// Compute upper bound on number of digit groups and allocate space
39774008
int maxNumDigitGroups = (4*mag.length + 6)/7;
3978-
String digitGroup[] = new String[maxNumDigitGroups];
4009+
long[] digitGroups = new long[maxNumDigitGroups];
39794010

39804011
// Translate number to string, a digit group at a time
3981-
BigInteger tmp = this.abs();
4012+
BigInteger tmp = this;
39824013
int numGroups = 0;
39834014
while (tmp.signum != 0) {
39844015
BigInteger d = longRadix[radix];
@@ -3990,33 +4021,37 @@ private String smallToString(int radix) {
39904021
BigInteger q2 = q.toBigInteger(tmp.signum * d.signum);
39914022
BigInteger r2 = r.toBigInteger(tmp.signum * d.signum);
39924023

3993-
digitGroup[numGroups++] = Long.toString(r2.longValue(), radix);
4024+
digitGroups[numGroups++] = r2.longValue();
39944025
tmp = q2;
39954026
}
39964027

3997-
// Put sign (if any) and first digit group into result buffer
3998-
StringBuilder buf = new StringBuilder(numGroups*digitsPerLong[radix]+1);
3999-
if (signum < 0) {
4000-
buf.append('-');
4001-
}
4002-
buf.append(digitGroup[numGroups-1]);
4028+
// Get string version of first digit group
4029+
String s = Long.toString(digitGroups[numGroups-1], radix);
4030+
4031+
// Pad with internal zeros if necessary.
4032+
padWithZeros(buf, digits - (s.length() +
4033+
(numGroups - 1)*digitsPerLong[radix]));
40034034

4004-
// Append remaining digit groups padded with leading zeros
4035+
// Put first digit group into result buffer
4036+
buf.append(s);
4037+
4038+
// Append remaining digit groups each padded with leading zeros
40054039
for (int i=numGroups-2; i >= 0; i--) {
40064040
// Prepend (any) leading zeros for this digit group
4007-
int numLeadingZeros = digitsPerLong[radix]-digitGroup[i].length();
4041+
s = Long.toString(digitGroups[i], radix);
4042+
int numLeadingZeros = digitsPerLong[radix] - s.length();
40084043
if (numLeadingZeros != 0) {
4009-
buf.append(zeros[numLeadingZeros]);
4044+
buf.append(ZEROS, 0, numLeadingZeros);
40104045
}
4011-
buf.append(digitGroup[i]);
4046+
buf.append(s);
40124047
}
4013-
return buf.toString();
40144048
}
40154049

40164050
/**
40174051
* Converts the specified BigInteger to a string and appends to
40184052
* {@code sb}. This implements the recursive Schoenhage algorithm
4019-
* for base conversions.
4053+
* for base conversions. This method can only be called for non-negative
4054+
* numbers.
40204055
* <p>
40214056
* See Knuth, Donald, _The Art of Computer Programming_, Vol. 2,
40224057
* Answers to Exercises (4.4) Question 14.
@@ -4026,40 +4061,34 @@ private String smallToString(int radix) {
40264061
* @param radix The base to convert to.
40274062
* @param digits The minimum number of digits to pad to.
40284063
*/
4029-
private static void toString(BigInteger u, StringBuilder sb, int radix,
4030-
int digits) {
4064+
private static void toString(BigInteger u, StringBuilder sb,
4065+
int radix, int digits) {
4066+
assert u.signum() >= 0;
4067+
40314068
// If we're smaller than a certain threshold, use the smallToString
4032-
// method, padding with leading zeroes when necessary.
4069+
// method, padding with leading zeroes when necessary unless we're
4070+
// at the beginning of the string or digits <= 0. As u.signum() >= 0,
4071+
// smallToString() will not prepend a negative sign.
40334072
if (u.mag.length <= SCHOENHAGE_BASE_CONVERSION_THRESHOLD) {
4034-
String s = u.smallToString(radix);
4035-
4036-
// Pad with internal zeros if necessary.
4037-
// Don't pad if we're at the beginning of the string.
4038-
if ((s.length() < digits) && (sb.length() > 0)) {
4039-
for (int i=s.length(); i < digits; i++) {
4040-
sb.append('0');
4041-
}
4042-
}
4043-
4044-
sb.append(s);
4073+
u.smallToString(radix, sb, digits);
40454074
return;
40464075
}
40474076

4048-
int b, n;
4049-
b = u.bitLength();
4050-
40514077
// Calculate a value for n in the equation radix^(2^n) = u
40524078
// and subtract 1 from that value. This is used to find the
40534079
// cache index that contains the best value to divide u.
4054-
n = (int) Math.round(Math.log(b * LOG_TWO / logCache[radix]) / LOG_TWO - 1.0);
4080+
int b = u.bitLength();
4081+
int n = (int) Math.round(Math.log(b * LOG_TWO / logCache[radix]) /
4082+
LOG_TWO - 1.0);
4083+
40554084
BigInteger v = getRadixConversionCache(radix, n);
40564085
BigInteger[] results;
40574086
results = u.divideAndRemainder(v);
40584087

40594088
int expectedDigits = 1 << n;
40604089

40614090
// Now recursively build the two halves of each number.
4062-
toString(results[0], sb, radix, digits-expectedDigits);
4091+
toString(results[0], sb, radix, digits - expectedDigits);
40634092
toString(results[1], sb, radix, expectedDigits);
40644093
}
40654094

@@ -4091,14 +4120,11 @@ private static BigInteger getRadixConversionCache(int radix, int exponent) {
40914120
return cacheLine[exponent];
40924121
}
40934122

4094-
/* zero[i] is a string of i consecutive zeros. */
4095-
private static String zeros[] = new String[64];
4096-
static {
4097-
zeros[63] =
4098-
"000000000000000000000000000000000000000000000000000000000000000";
4099-
for (int i=0; i < 63; i++)
4100-
zeros[i] = zeros[63].substring(0, i);
4101-
}
4123+
/* Size of ZEROS string. */
4124+
private static int NUM_ZEROS = 63;
4125+
4126+
/* ZEROS is a string of NUM_ZEROS consecutive zeros. */
4127+
private static final String ZEROS = "0".repeat(NUM_ZEROS);
41024128

41034129
/**
41044130
* Returns the decimal String representation of this BigInteger.

test/jdk/java/math/BigInteger/BigIntegerTest.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,7 @@
2626
* @library /test/lib
2727
* @build jdk.test.lib.RandomFactory
2828
* @run main BigIntegerTest
29-
* @bug 4181191 4161971 4227146 4194389 4823171 4624738 4812225 4837946 4026465 8074460 8078672 8032027
29+
* @bug 4181191 4161971 4227146 4194389 4823171 4624738 4812225 4837946 4026465 8074460 8078672 8032027 8229845
3030
* @summary tests methods in BigInteger (use -Dseed=X to set PRNG seed)
3131
* @run main/timeout=400 BigIntegerTest
3232
* @author madbot
@@ -795,7 +795,7 @@ public static void stringConv() {
795795

796796
// Generic string conversion.
797797
for (int i=0; i<100; i++) {
798-
byte xBytes[] = new byte[Math.abs(random.nextInt())%100+1];
798+
byte xBytes[] = new byte[Math.abs(random.nextInt())%200+1];
799799
random.nextBytes(xBytes);
800800
BigInteger x = new BigInteger(xBytes);
801801

@@ -836,6 +836,16 @@ public static void stringConv() {
836836
}
837837
}
838838

839+
// Check value with many trailing zeros.
840+
String val = "123456789" + "0".repeat(200);
841+
BigInteger b = new BigInteger(val);
842+
String s = b.toString();
843+
if (!val.equals(s)) {
844+
System.err.format("Expected length %d but got %d%n",
845+
val.length(), s.length());
846+
failCount++;
847+
}
848+
839849
report("String Conversion", failCount);
840850
}
841851

0 commit comments

Comments
 (0)