8007398: Peformance improvements to Integer and Long string formatting.
Reviewed-by: mduigou, martin, darcy, briangoetz
Contributed-by: Steven Schlansker <stevenschlansker@gmail.com>, Mike Duigou <mike.duigou@oracle.com>
diff --git a/src/share/classes/java/lang/Integer.java b/src/share/classes/java/lang/Integer.java
index a0eeecf..e5de967 100644
--- a/src/share/classes/java/lang/Integer.java
+++ b/src/share/classes/java/lang/Integer.java
@@ -26,7 +26,6 @@
package java.lang;
import java.lang.annotation.Native;
-import java.util.Properties;
/**
* The {@code Integer} class wraps a value of the primitive type
@@ -185,7 +184,7 @@
* @since 1.8
*/
public static String toUnsignedString(int i, int radix) {
- return Long.toString(toUnsignedLong(i), radix);
+ return Long.toUnsignedString(toUnsignedLong(i), radix);
}
/**
@@ -307,20 +306,39 @@
/**
* Convert the integer to an unsigned number.
*/
- private static String toUnsignedString0(int i, int shift) {
- char[] buf = new char[32];
- int charPos = 32;
+ private static String toUnsignedString0(int val, int shift) {
+ // assert shift > 0 && shift <=5 : "Illegal shift value";
+ int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
+ int chars = Math.max(((mag + (shift - 1)) / shift), 1);
+ char[] buf = new char[chars];
+
+ formatUnsignedInt(val, shift, buf, 0, chars);
+
+ // Use special constructor which takes over "buf".
+ return new String(buf, true);
+ }
+
+ /**
+ * Format a long (treated as unsigned) into a character buffer.
+ * @param val the unsigned int to format
+ * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary)
+ * @param buf the character buffer to write to
+ * @param offset the offset in the destination buffer to start at
+ * @param len the number of characters to write
+ * @return the lowest character location used
+ */
+ static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
+ int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
- buf[--charPos] = digits[i & mask];
- i >>>= shift;
- } while (i != 0);
+ buf[offset + --charPos] = Integer.digits[val & mask];
+ val >>>= shift;
+ } while (val != 0 && charPos > 0);
- return new String(buf, charPos, (32 - charPos));
+ return charPos;
}
-
final static char [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
@@ -875,6 +893,7 @@
* Returns the value of this {@code Integer} as a {@code long}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
+ * @see Integer#toUnsignedLong(int)
*/
public long longValue() {
return (long)value;
diff --git a/src/share/classes/java/lang/Long.java b/src/share/classes/java/lang/Long.java
index 76dbfc3..80967ac 100644
--- a/src/share/classes/java/lang/Long.java
+++ b/src/share/classes/java/lang/Long.java
@@ -28,6 +28,7 @@
import java.lang.annotation.Native;
import java.math.*;
+
/**
* The {@code Long} class wraps a value of the primitive type {@code
* long} in an object. An object of type {@code Long} contains a
@@ -344,18 +345,39 @@
}
/**
- * Convert the integer to an unsigned number.
+ * Format a long (treated as unsigned) into a String.
+ * @param val the value to format
+ * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary)
*/
- private static String toUnsignedString0(long i, int shift) {
- char[] buf = new char[64];
- int charPos = 64;
+ static String toUnsignedString0(long val, int shift) {
+ // assert shift > 0 && shift <=5 : "Illegal shift value";
+ int mag = Long.SIZE - Long.numberOfLeadingZeros(val);
+ int chars = Math.max(((mag + (shift - 1)) / shift), 1);
+ char[] buf = new char[chars];
+
+ formatUnsignedLong(val, shift, buf, 0, chars);
+ return new String(buf, true);
+ }
+
+ /**
+ * Format a long (treated as unsigned) into a character buffer.
+ * @param val the unsigned long to format
+ * @param shift the log2 of the base to format in (4 for hex, 3 for octal, 1 for binary)
+ * @param buf the character buffer to write to
+ * @param offset the offset in the destination buffer to start at
+ * @param len the number of characters to write
+ * @return the lowest character location used
+ */
+ static int formatUnsignedLong(long val, int shift, char[] buf, int offset, int len) {
+ int charPos = len;
int radix = 1 << shift;
- long mask = radix - 1;
+ int mask = radix - 1;
do {
- buf[--charPos] = Integer.digits[(int)(i & mask)];
- i >>>= shift;
- } while (i != 0);
- return new String(buf, charPos, (64 - charPos));
+ buf[offset + --charPos] = Integer.digits[((int) val) & mask];
+ val >>>= shift;
+ } while (val != 0 && charPos > 0);
+
+ return charPos;
}
/**
diff --git a/test/java/lang/IntegralPrimitiveToString.java b/test/java/lang/IntegralPrimitiveToString.java
new file mode 100644
index 0000000..0135cd2
--- /dev/null
+++ b/test/java/lang/IntegralPrimitiveToString.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2012, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.LongFunction;
+import java.util.function.Function;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * @test
+ * @run testng IntegralPrimitiveToString
+ * @summary test string conversions for primitive integral types.
+ * @author Mike Duigou
+ */
+public class IntegralPrimitiveToString {
+
+ @Test(dataProvider="numbers")
+ public <N extends Number> void testToString(String description,
+ Function<N, BigInteger> converter,
+ Function<N, BigInteger> unsignedConverter,
+ N[] values,
+ Stringifier<N>[] stringifiers) {
+ System.out.printf("%s : conversions: %d values: %d\n", description, stringifiers.length, values.length);
+ for( N value : values) {
+ BigInteger asBigInt = converter.apply(value);
+ BigInteger asUnsignedBigInt = unsignedConverter.apply(value);
+ for(Stringifier<N> stringifier : stringifiers) {
+ stringifier.assertMatchingToString(value, asBigInt, asUnsignedBigInt, description);
+ }
+ }
+ }
+
+ static class Stringifier<N extends Number> {
+ final boolean signed;
+ final int radix;
+ final Function<N,String> toString;
+ Stringifier(boolean signed, int radix, Function<N,String> toString) {
+ this.signed = signed;
+ this.radix = radix;
+ this.toString = toString;
+ }
+
+ public void assertMatchingToString(N value, BigInteger asSigned, BigInteger asUnsigned, String description) {
+ String expected = signed
+ ? asSigned.toString(radix)
+ : asUnsigned.toString(radix);
+
+ String actual = toString.apply(value);
+
+ assertEquals(actual, expected, description + " conversion should be the same");
+ }
+ }
+
+ @DataProvider(name="numbers", parallel=true)
+ public Iterator<Object[]> testSetProvider() {
+
+ return Arrays.asList(
+ new Object[] { "Byte",
+ (Function<Byte,BigInteger>) b -> BigInteger.valueOf((long) b),
+ (Function<Byte,BigInteger>) b -> BigInteger.valueOf(Integer.toUnsignedLong((byte) b)),
+ numberProvider((LongFunction<Byte>) l -> Byte.valueOf((byte) l), Byte.SIZE),
+ new Stringifier[] {
+ new Stringifier<Byte>(true, 10, b -> b.toString()),
+ new Stringifier<Byte>(true, 10, b -> Byte.toString(b))
+ }
+ },
+ new Object[] { "Short",
+ (Function<Short,BigInteger>) s -> BigInteger.valueOf((long) s),
+ (Function<Short,BigInteger>) s -> BigInteger.valueOf(Integer.toUnsignedLong((short) s)),
+ numberProvider((LongFunction<Short>) l -> Short.valueOf((short) l), Short.SIZE),
+ new Stringifier[] {
+ new Stringifier<Short>(true, 10, s -> s.toString()),
+ new Stringifier<Short>(true, 10, s -> Short.toString( s))
+ }
+ },
+ new Object[] { "Integer",
+ (Function<Integer,BigInteger>) i -> BigInteger.valueOf((long) i),
+ (Function<Integer,BigInteger>) i -> BigInteger.valueOf(Integer.toUnsignedLong(i)),
+ numberProvider((LongFunction<Integer>) l -> Integer.valueOf((int) l), Integer.SIZE),
+ new Stringifier[] {
+ new Stringifier<Integer>(true, 10, i -> i.toString()),
+ new Stringifier<Integer>(true, 10, i -> Integer.toString(i)),
+ new Stringifier<Integer>(false, 2, Integer::toBinaryString),
+ new Stringifier<Integer>(false, 16, Integer::toHexString),
+ new Stringifier<Integer>(false, 8, Integer::toOctalString),
+ new Stringifier<Integer>(true, 2, i -> Integer.toString(i, 2)),
+ new Stringifier<Integer>(true, 8, i -> Integer.toString(i, 8)),
+ new Stringifier<Integer>(true, 10, i -> Integer.toString(i, 10)),
+ new Stringifier<Integer>(true, 16, i -> Integer.toString(i, 16)),
+ new Stringifier<Integer>(true, Character.MAX_RADIX, i -> Integer.toString(i, Character.MAX_RADIX)),
+ new Stringifier<Integer>(false, 10, i -> Integer.toUnsignedString(i)),
+ new Stringifier<Integer>(false, 2, i -> Integer.toUnsignedString(i, 2)),
+ new Stringifier<Integer>(false, 8, i -> Integer.toUnsignedString(i, 8)),
+ new Stringifier<Integer>(false, 10, i -> Integer.toUnsignedString(i, 10)),
+ new Stringifier<Integer>(false, 16, i -> Integer.toUnsignedString(i, 16)),
+ new Stringifier<Integer>(false, Character.MAX_RADIX, i -> Integer.toUnsignedString(i, Character.MAX_RADIX))
+ }
+ },
+ new Object[] { "Long",
+ (Function<Long, BigInteger>) BigInteger::valueOf,
+ (Function<Long, BigInteger>) l -> {
+ if (l >= 0) {
+ return BigInteger.valueOf((long) l);
+ } else {
+ int upper = (int)(l >>> 32);
+ int lower = (int) (long) l;
+
+ // return (upper << 32) + lower
+ return (BigInteger.valueOf(Integer.toUnsignedLong(upper))).shiftLeft(32).
+ add(BigInteger.valueOf(Integer.toUnsignedLong(lower)));
+ }
+ },
+ numberProvider((LongFunction<Long>) Long::valueOf, Long.SIZE),
+ new Stringifier[] {
+ new Stringifier<Long>(true, 10, l -> l.toString()),
+ new Stringifier<Long>(true, 10, l -> Long.toString(l)),
+ new Stringifier<Long>(false, 2, Long::toBinaryString),
+ new Stringifier<Long>(false, 16, Long::toHexString),
+ new Stringifier<Long>(false, 8, Long::toOctalString),
+ new Stringifier<Long>(true, 2, l -> Long.toString(l, 2)),
+ new Stringifier<Long>(true, 8, l -> Long.toString(l, 8)),
+ new Stringifier<Long>(true, 10, l -> Long.toString(l, 10)),
+ new Stringifier<Long>(true, 16, l -> Long.toString(l, 16)),
+ new Stringifier<Long>(true, Character.MAX_RADIX, l -> Long.toString(l, Character.MAX_RADIX)),
+ new Stringifier<Long>(false, 10, Long::toUnsignedString),
+ new Stringifier<Long>(false, 2, l -> Long.toUnsignedString(l, 2)),
+ new Stringifier<Long>(false, 8, l-> Long.toUnsignedString(l, 8)),
+ new Stringifier<Long>(false, 10, l -> Long.toUnsignedString(l, 10)),
+ new Stringifier<Long>(false, 16, l -> Long.toUnsignedString(l, 16)),
+ new Stringifier<Long>(false, Character.MAX_RADIX, l -> Long.toUnsignedString(l, Character.MAX_RADIX))
+ }
+ }
+ ).iterator();
+ }
+ private static final long[] SOME_PRIMES = {
+ 3L, 5L, 7L, 11L, 13L, 17L, 19L, 23L, 29L, 31L, 37L, 41L, 43L, 47L, 53L,
+ 59L, 61L, 71L, 73L, 79L, 83L, 89L, 97L, 101L, 103L, 107L, 109L, 113L,
+ 5953L, 5981L, 5987L, 6007L, 6011L, 6029L, 6037L, 6043L, 6047L, 6053L,
+ 16369L, 16381L, 16411L, 32749L, 32771L, 65521L, 65537L,
+ (long) Integer.MAX_VALUE };
+
+ public <N extends Number> N[] numberProvider(LongFunction<N> boxer, int bits, N... extras) {
+ List<N> numbers = new ArrayList<>();
+
+ for(int bitmag = 0; bitmag < bits; bitmag++) {
+ long value = 1L << bitmag;
+ numbers.add(boxer.apply(value));
+ numbers.add(boxer.apply(value - 1));
+ numbers.add(boxer.apply(value + 1));
+ numbers.add(boxer.apply(-value));
+ for(int divisor = 0; divisor < SOME_PRIMES.length && value < SOME_PRIMES[divisor]; divisor++) {
+ numbers.add(boxer.apply(value - SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value + SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value * SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value / SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value | SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value & SOME_PRIMES[divisor]));
+ numbers.add(boxer.apply(value ^ SOME_PRIMES[divisor]));
+ }
+ }
+
+ numbers.addAll(Arrays.asList(extras));
+
+ return (N[]) numbers.toArray(new Number[numbers.size()]);
+ }
+}