Mercurial > hg > openjdk > jdk9 > jdk
changeset 7286:c8410ce73ad6
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>
author | mduigou |
---|---|
date | Tue, 12 Feb 2013 17:04:09 -0800 |
parents | 198de8103df2 |
children | f3c7c5f753dc |
files | src/share/classes/java/lang/Integer.java src/share/classes/java/lang/Long.java test/java/lang/IntegralPrimitiveToString.java |
diffstat | 3 files changed, 254 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/java/lang/Integer.java Fri May 31 17:31:40 2013 -0700 +++ b/src/share/classes/java/lang/Integer.java Tue Feb 12 17:04:09 2013 -0800 @@ -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;
--- a/src/share/classes/java/lang/Long.java Fri May 31 17:31:40 2013 -0700 +++ b/src/share/classes/java/lang/Long.java Tue Feb 12 17:04:09 2013 -0800 @@ -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; } /**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/lang/IntegralPrimitiveToString.java Tue Feb 12 17:04:09 2013 -0800 @@ -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()]); + } +}