From fdc4686478e1130e08c351d9f8a9288e39db6d47 Mon Sep 17 00:00:00 2001 From: Andrei Korzhevskii Date: Thu, 24 Mar 2016 11:48:54 +0200 Subject: [PATCH] Emulate unsigned numbers Java 8 API. Some code and tests adapted from google guava libraries. Change-Id: Ibcc1a9253f734a11e08c964b5fc067c234ddf8ac --- .../com/google/gwt/emul/java/lang/Byte.java | 10 +- .../google/gwt/emul/java/lang/Character.java | 2 +- .../google/gwt/emul/java/lang/Integer.java | 57 ++++- .../com/google/gwt/emul/java/lang/Long.java | 151 ++++++++++++- .../com/google/gwt/emul/java/lang/Number.java | 212 ++++++++++++++++-- .../com/google/gwt/emul/java/lang/Short.java | 10 +- .../gwt/emul/javaemul/internal/Coercions.java | 7 + .../gwt/emul/javaemul/internal/JsUtils.java | 4 + .../gwt/emultest/java/lang/ByteTest.java | 25 +++ .../gwt/emultest/java/lang/IntegerTest.java | 141 +++++++++++- .../gwt/emultest/java/lang/LongTest.java | 149 ++++++++++++ .../gwt/emultest/java/lang/ShortTest.java | 26 +++ 12 files changed, 763 insertions(+), 31 deletions(-) diff --git a/user/super/com/google/gwt/emul/java/lang/Byte.java b/user/super/com/google/gwt/emul/java/lang/Byte.java index d5f0f1f93d7..0219116f1f6 100644 --- a/user/super/com/google/gwt/emul/java/lang/Byte.java +++ b/user/super/com/google/gwt/emul/java/lang/Byte.java @@ -31,7 +31,7 @@ public final class Byte extends Number implements Comparable { */ private static class BoxedValues { // Box all values according to JLS - private static Byte[] boxedValues = new Byte[256]; + private static final Byte[] boxedValues = new Byte[256]; // This method should be marked with @HasNoSideEffects but it seems to trigger a bug // in the optimizing pipeling and breaks one test. @@ -70,6 +70,14 @@ public static String toString(byte b) { return String.valueOf(b); } + public static int toUnsignedInt(byte b) { + return b & 0xff; + } + + public static long toUnsignedLong(byte b) { + return toUnsignedInt(b); + } + public static Byte valueOf(byte b) { return BoxedValues.get(b); } diff --git a/user/super/com/google/gwt/emul/java/lang/Character.java b/user/super/com/google/gwt/emul/java/lang/Character.java index d675b1719e6..157e04bade9 100644 --- a/user/super/com/google/gwt/emul/java/lang/Character.java +++ b/user/super/com/google/gwt/emul/java/lang/Character.java @@ -104,7 +104,7 @@ public java.lang.CharSequence subSequence(int start, int end) { */ private static class BoxedValues { // Box values according to JLS - from \u0000 to \u007f - private static Character[] boxedValues = new Character[128]; + private static final Character[] boxedValues = new Character[128]; @HasNoSideEffects private static Character get(char c) { diff --git a/user/super/com/google/gwt/emul/java/lang/Integer.java b/user/super/com/google/gwt/emul/java/lang/Integer.java index a56e31e4234..03f22d7b954 100644 --- a/user/super/com/google/gwt/emul/java/lang/Integer.java +++ b/user/super/com/google/gwt/emul/java/lang/Integer.java @@ -15,6 +15,9 @@ */ package java.lang; +import static javaemul.internal.Coercions.ensureInt; +import static javaemul.internal.Coercions.toUnsignedInt; + import javaemul.internal.JsUtils; import javaemul.internal.annotations.HasNoSideEffects; @@ -34,7 +37,7 @@ public final class Integer extends Number implements Comparable { */ private static class BoxedValues { // Box values according to JLS - between -128 and 127 - private static Integer[] boxedValues = new Integer[256]; + private static final Integer[] boxedValues = new Integer[256]; @HasNoSideEffects private static Integer get(int i) { @@ -81,10 +84,18 @@ public static int compare(int x, int y) { } } + public static int compareUnsigned(int a, int b) { + return compare(a ^ MIN_VALUE, b ^ MIN_VALUE); + } + public static Integer decode(String s) throws NumberFormatException { return Integer.valueOf(__decodeAndValidateInt(s, MIN_VALUE, MAX_VALUE)); } + public static int divideUnsigned(int dividend, int divisor) { + return ensureInt(toUnsignedInt(dividend) / toUnsignedInt(divisor)); + } + public static int hashCode(int i) { return i; } @@ -170,6 +181,37 @@ public static int parseInt(String s, int radix) throws NumberFormatException { return __parseAndValidateInt(s, radix, MIN_VALUE, MAX_VALUE); } + public static int parseUnsignedInt(String s) throws NumberFormatException { + return parseUnsignedInt(s, 10); + } + + public static int parseUnsignedInt(String s, int radix) throws NumberFormatException { + if (s == null) { + throw NumberFormatException.forNullInputString(); + } + + int len = s.length(); + if (len == 0 || s.charAt(0) == '-') { + throw NumberFormatException.forInputString(s); + } + + // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits + // Integer.MAX_VALUE in base 10 is 10 digits + if (len <= 5 || (radix == 10 && len <= 9)) { + return parseInt(s, radix); + } + + long value = Long.parseLong(s, radix); + if (!Long.fitsInUint(value)) { + throw NumberFormatException.forInputString(s); + } + return (int) value; + } + + public static int remainderUnsigned(int dividend, int divisor) { + return ensureInt(toUnsignedInt(dividend) % toUnsignedInt(divisor)); + } + public static int reverse(int i) { int[] nibbles = ReverseNibbles.reverseNibbles; return (nibbles[i >>> 28]) | (nibbles[(i >> 24) & 0xf] << 4) @@ -249,13 +291,20 @@ public static Integer valueOf(int i) { return new Integer(i); } + public static long toUnsignedLong(int x) { + return x & 0xffff_ffffL; + } + + public static String toUnsignedString(int x) { + return toUnsignedString(x, 10); + } + public static Integer valueOf(String s) throws NumberFormatException { return valueOf(s, 10); } - public static Integer valueOf(String s, int radix) - throws NumberFormatException { - return Integer.valueOf(Integer.parseInt(s, radix)); + public static Integer valueOf(String s, int radix) throws NumberFormatException { + return valueOf(parseInt(s, radix)); } private final transient int value; diff --git a/user/super/com/google/gwt/emul/java/lang/Long.java b/user/super/com/google/gwt/emul/java/lang/Long.java index 8950a41236f..9122452e20b 100644 --- a/user/super/com/google/gwt/emul/java/lang/Long.java +++ b/user/super/com/google/gwt/emul/java/lang/Long.java @@ -24,7 +24,7 @@ public final class Long extends Number implements Comparable { /** Use nested class to avoid clinit on outer. */ static class BoxedValues { // Box values according to JLS - between -128 and 127 - static Long[] boxedValues = new Long[256]; + static final Long[] boxedValues = new Long[256]; @HasNoSideEffects private static Long get(long l) { @@ -59,11 +59,44 @@ public static int compare(long x, long y) { } } + public static int compareUnsigned(long a, long b) { + return compare(a ^ MIN_VALUE, b ^ MIN_VALUE); + } + public static Long decode(String s) throws NumberFormatException { __Decode decode = __decodeNumberString(s); return valueOf(decode.payload, decode.radix); } + public static long divideUnsigned(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + if (compare(rem, divisor) >= 0) { + return quotient + 1; + } else { + return quotient; + } + } + public static int hashCode(long l) { return LongUtils.getHighBits(l) ^ (int) l; } @@ -121,6 +154,43 @@ public static long reverse(long l) { return LongUtils.fromBits(Integer.reverse(high), Integer.reverse(low)); } + public static long parseUnsignedLong(String s) throws NumberFormatException { + return parseUnsignedLong(s, 10); + } + + public static long parseUnsignedLong(String s, int radix) throws NumberFormatException { + return __parseAndValidateUnsignedLong(s, radix); + } + + public static long remainderUnsigned(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + if (compare(rem, divisor) >= 0) { + return rem - divisor; + } else { + return rem; + } + } + public static long reverseBytes(long l) { int high = LongUtils.getHighBits(l); int low = (int) l; @@ -163,15 +233,15 @@ public static long sum(long a, long b) { } public static String toBinaryString(long value) { - return toPowerOfTwoUnsignedString(value, 1); + return toUnsignedString(value, 2); } public static String toHexString(long value) { - return toPowerOfTwoUnsignedString(value, 4); + return toUnsignedString(value, 16); } public static String toOctalString(long value) { - return toPowerOfTwoUnsignedString(value, 3); + return toUnsignedString(value, 8); } public static String toString(long value) { @@ -219,17 +289,59 @@ public static String toString(long value, int intRadix) { public static Long valueOf(long l) { if (l > -129 && l < 128) { - return BoxedValues.get(l); + return BoxedValues.get(l); } return new Long(l); } - public static Long valueOf(String s) throws NumberFormatException { - return valueOf(s, 10); + public static String toUnsignedString(long value) { + return toUnsignedString(value, 10); } - public static Long valueOf(String s, int radix) throws NumberFormatException { - return valueOf(parseLong(s, radix)); + public static String toUnsignedString(long value, int intRadix) { + if (fitsInUint(value)) { + return Integer.toUnsignedString((int) value, intRadix); + } + + if (intRadix < Character.MIN_RADIX || intRadix > Character.MAX_RADIX) { + intRadix = 10; + } + + if (isPowerOfTwo(intRadix)) { + return toPowerOfTwoUnsignedString(value, intRadix); + } + + if (value >= 0) { + return toString(value, intRadix); + } + + // Convert radix to long before hand to avoid costly conversion on each iteration. + long radix = intRadix; + if (intRadix == 10) { + long quotient = divideUnsigned(value, radix); + int rem = (int) (value - quotient * radix); + return toString(quotient) + rem; + } + + int bufLen = intRadix < 8 ? 65 : 23; // Max chars in result (conservative) + char[] buf = new char[bufLen]; + int cursor = bufLen; + if (value < 0) { + // Separate off the last digit using unsigned division. That will leave + // a number that is nonnegative as a signed integer. + long quotient = divideUnsigned(value, radix); + int rem = (int) (value - quotient * radix); + buf[--cursor] = Character.forDigit(rem, intRadix); + value = quotient; + } + + // Simple modulo/division approach + while (value > 0) { + buf[--cursor] = Character.forDigit((int) (value % radix), intRadix); + value /= radix; + } + + return new String(buf, cursor, buf.length - cursor); } private static String toPowerOfTwoUnsignedString(long value, int shift) { @@ -252,6 +364,27 @@ private static String toPowerOfTwoUnsignedString(long value, int shift) { return String.valueOf(buf, pos, bufSize - pos); } + private static boolean isPowerOfTwo(int x) { + return (x & (x - 1)) == 0; + } + + private static int log2(int x) { + return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x); + } + + static boolean fitsInUint(long value) { + int highBits = (int) (value >> 32); + return highBits == 0; + } + + public static Long valueOf(String s) throws NumberFormatException { + return valueOf(s, 10); + } + + public static Long valueOf(String s, int radix) throws NumberFormatException { + return valueOf(parseLong(s, radix)); + } + private final transient long value; public Long(long value) { diff --git a/user/super/com/google/gwt/emul/java/lang/Number.java b/user/super/com/google/gwt/emul/java/lang/Number.java index 3be1cdbe195..11175baae5d 100644 --- a/user/super/com/google/gwt/emul/java/lang/Number.java +++ b/user/super/com/google/gwt/emul/java/lang/Number.java @@ -135,6 +135,131 @@ private static class JavaLangNumber { } return "number".equals(JsUtils.typeOf(instance)) || instance instanceof JavaLangNumber; } + /** + * Use nested class to avoid clinit on outer. + */ + static class __ParseUnsignedLong { + /** + * The largest number of digits (excluding leading zeros) that + * can fit into an unsigned long for a given radix between 2 and 36, inclusive. + */ + private static final int[] maxLengthForRadix = {-1, -1, // unused + 64, // base 2 + 41, // base 3 + 32, // base 4 + 28, // base 5 + 25, // base 6 + 23, // base 7 + 22, // base 8 + 21, // base 9 + 20, // base 10 + 19, // base 11 + 18, // base 12 + 18, // base 13 + 17, // base 14 + 17, // base 15 + 16, // base 16 + 16, // base 17 + 16, // base 18 + 16, // base 19 + 15, // base 20 + 15, // base 21 + 15, // base 22 + 15, // base 23 + 14, // base 24 + 14, // base 25 + 14, // base 26 + 14, // base 27 + 14, // base 28 + 14, // base 29 + 14, // base 30 + 13, // base 31 + 13, // base 32 + 13, // base 33 + 13, // base 34 + 13, // base 35 + 13, // base 36 + }; + + // Calculated as 0xffffffffffffffff / radix. + private static final long[] maxDividendForRadix = {-1, -1, // unused + 9223372036854775807L, // base 2 + 6148914691236517205L, // base 3 + 4611686018427387903L, // base 4 + 3689348814741910323L, // base 5 + 3074457345618258602L, // base 6 + 2635249153387078802L, // base 7 + 2305843009213693951L, // base 8 + 2049638230412172401L, // base 9 + 1844674407370955161L, // base 10 + 1676976733973595601L, // base 11 + 1537228672809129301L, // base 12 + 1418980313362273201L, // base 13 + 1317624576693539401L, // base 14 + 1229782938247303441L, // base 15 + 1152921504606846975L, // base 16 + 1085102592571150095L, // base 17 + 1024819115206086200L, // base 18 + 970881267037344821L, // base 19 + 922337203685477580L, // base 20 + 878416384462359600L, // base 21 + 838488366986797800L, // base 22 + 802032351030850070L, // base 23 + 768614336404564650L, // base 24 + 737869762948382064L, // base 25 + 709490156681136600L, // base 26 + 683212743470724133L, // base 27 + 658812288346769700L, // base 28 + 636094623231363848L, // base 29 + 614891469123651720L, // base 30 + 595056260442243600L, // base 31 + 576460752303423487L, // base 32 + 558992244657865200L, // base 33 + 542551296285575047L, // base 34 + 527049830677415760L, // base 35 + 512409557603043100L, // base 36 + }; + + // Calculated as 0xffffffffffffffff % radix. + private static final int[] maxRemainderForRadix = {-1, -1, // unused + 1, // base 2 + 0, // base 3 + 3, // base 4 + 0, // base 5 + 3, // base 6 + 1, // base 7 + 7, // base 8 + 6, // base 9 + 5, // base 10 + 4, // base 11 + 3, // base 12 + 2, // base 13 + 1, // base 14 + 0, // base 15 + 15, // base 16 + 0, // base 17 + 15, // base 18 + 16, // base 19 + 15, // base 20 + 15, // base 21 + 15, // base 22 + 5, // base 23 + 15, // base 24 + 15, // base 25 + 15, // base 26 + 24, // base 27 + 15, // base 28 + 23, // base 29 + 15, // base 30 + 15, // base 31 + 31, // base 32 + 15, // base 33 + 17, // base 34 + 15, // base 35 + 15, // base 36 + }; + } + /** * @skip * @@ -218,12 +343,13 @@ protected static int __parseAndValidateInt(String s, int radix, int lowerBound, } int toReturn = JsUtils.parseInt(s, radix); + if (Double.isNaN(toReturn)) { + throw NumberFormatException.forInputString(s); + } // isTooLow is separated into its own variable to avoid a bug in BlackBerry OS 7. See // https://code.google.com/p/google-web-toolkit/issues/detail?id=7291. boolean isTooLow = toReturn < lowerBound; - if (Double.isNaN(toReturn)) { - throw NumberFormatException.forInputString(s); - } else if (isTooLow || toReturn > upperBound) { + if (isTooLow || toReturn > upperBound) { throw NumberFormatException.forInputString(s); } @@ -236,16 +362,25 @@ protected static int __parseAndValidateInt(String s, int radix, int lowerBound, * This function contains common logic for parsing a String in a given radix * and validating the result. */ - protected static long __parseAndValidateLong(String s, int radix) throws NumberFormatException { - if (s == null) { + protected static long __parseAndValidateLong(final String original, int radix) + throws NumberFormatException { + String s = validateDecimalAndStripZeroes(original, radix, __ParseLong.maxLengthForRadix); + return __unsafeParseLong(s, radix, original); + } + + /** + * Validates string representation of the decimal and strips leading zeroes. + */ + private static String validateDecimalAndStripZeroes(final String original, int radix, + int[] maxDigitsForRadix) { + if (original == null) { throw NumberFormatException.forNullInputString(); } if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { throw NumberFormatException.forRadix(radix); } - final String orig = s; - + String s = original; int length = s.length(); boolean negative = false; if (length > 0) { @@ -257,7 +392,7 @@ protected static long __parseAndValidateLong(String s, int radix) throws NumberF } } if (length == 0) { - throw NumberFormatException.forInputString(orig); + throw NumberFormatException.forInputString(original); } // Strip leading zeros @@ -268,22 +403,32 @@ protected static long __parseAndValidateLong(String s, int radix) throws NumberF // Immediately eject numbers that are too long -- this avoids more complex // overflow handling below - if (length > __ParseLong.maxLengthForRadix[radix]) { - throw NumberFormatException.forInputString(orig); + if (length > maxDigitsForRadix[radix]) { + throw NumberFormatException.forInputString(original); } // Validate the digits for (int i = 0; i < length; i++) { if (Character.digit(s.charAt(i), radix) == -1) { - throw NumberFormatException.forInputString(orig); + throw NumberFormatException.forInputString(original); } } + return negative ? '-' + s : s; + } + + private static long __unsafeParseLong(String s, int radix, final String original) { + boolean negative = s.charAt(0) == '-'; + if (negative) { + s = s.substring(1); + } + long toReturn = 0; int maxDigits = __ParseLong.maxDigitsForRadix[radix]; long radixPower = __ParseLong.maxDigitsRadixPower[radix]; long minValue = -__ParseLong.maxValueForRadix[radix]; + int length = s.length(); boolean firstTime = true; int head = length % maxDigits; if (head > 0) { @@ -302,7 +447,7 @@ protected static long __parseAndValidateLong(String s, int radix) throws NumberF if (!firstTime) { // Check whether multiplying by radixPower will overflow if (toReturn < minValue) { - throw NumberFormatException.forInputString(orig); + throw NumberFormatException.forInputString(original); } toReturn *= radixPower; } else { @@ -313,19 +458,58 @@ protected static long __parseAndValidateLong(String s, int radix) throws NumberF // A positive value means we overflowed Long.MIN_VALUE if (toReturn > 0) { - throw NumberFormatException.forInputString(orig); + throw NumberFormatException.forInputString(original); } if (!negative) { toReturn = -toReturn; // A negative value means we overflowed Long.MAX_VALUE if (toReturn < 0) { - throw NumberFormatException.forInputString(orig); + throw NumberFormatException.forInputString(original); } } return toReturn; } + /** + * @skip + * + * This function contains common logic for parsing a String in a given radix + * and validating the result. + */ + protected static long __parseAndValidateUnsignedLong(final String original, int radix) { + String s = validateDecimalAndStripZeroes(original, radix, __ParseUnsignedLong.maxLengthForRadix); + + if (s.charAt(0) == '-') { + throw NumberFormatException.forInputString(original); + } + + // extract last digit of the number to parse it as signed long and + // then combine them together. + int lastDigit = -1; + int length = s.length(); + if (length >= __ParseLong.maxLengthForRadix[radix]) { + lastDigit = Character.digit(s.charAt(length - 1), radix); + s = s.substring(0, length - 1); + } + + long value = __unsafeParseLong(s, radix, original); + if (lastDigit == -1) { + return value; + } + + // check for overflow + long maxDividend = __ParseUnsignedLong.maxDividendForRadix[radix]; + long maxRemainder = __ParseUnsignedLong.maxRemainderForRadix[radix]; + // if toReturn < 0 then highest bit is set and we're gonna overflow + if (value < 0 || + maxDividend < value || + (maxDividend == value && maxRemainder < lastDigit)) { + throw NumberFormatException.forInputString(original); + } + return value * radix + lastDigit; + } + /** * @skip * diff --git a/user/super/com/google/gwt/emul/java/lang/Short.java b/user/super/com/google/gwt/emul/java/lang/Short.java index 15e9b38e6d1..7c506919db5 100644 --- a/user/super/com/google/gwt/emul/java/lang/Short.java +++ b/user/super/com/google/gwt/emul/java/lang/Short.java @@ -33,7 +33,7 @@ public final class Short extends Number implements Comparable { */ private static class BoxedValues { // Box values according to JLS - between -128 and 127 - private static Short[] boxedValues = new Short[256]; + private static final Short[] boxedValues = new Short[256]; @HasNoSideEffects private static Short get(short s) { @@ -75,6 +75,14 @@ public static String toString(short b) { return String.valueOf(b); } + public static int toUnsignedInt(short s) { + return s & 0xffff; + } + + public static long toUnsignedLong(short s) { + return toUnsignedInt(s); + } + public static Short valueOf(short s) { if (s > -129 && s < 128) { return BoxedValues.get(s); diff --git a/user/super/com/google/gwt/emul/javaemul/internal/Coercions.java b/user/super/com/google/gwt/emul/javaemul/internal/Coercions.java index 267b98fa169..c39fad9f4af 100644 --- a/user/super/com/google/gwt/emul/javaemul/internal/Coercions.java +++ b/user/super/com/google/gwt/emul/javaemul/internal/Coercions.java @@ -30,5 +30,12 @@ public static int ensureInt(int value) { return value | 0; } + /** + * Convert int into js unsigned int. + */ + public static native int toUnsignedInt(int value) /*-{ + return (value >>> 0); + }-*/; + private Coercions() { } } diff --git a/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java b/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java index ba6cc5ff618..b901ad75324 100644 --- a/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java +++ b/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java @@ -69,6 +69,10 @@ private interface NativeNumber { String toPrecision(int precision); } + public static native String toString(int value, int radix) /*-{ + return value.toString(radix); + }-*/; + public static native boolean isUndefined(Object value) /*-{ return value === undefined; }-*/; diff --git a/user/test/com/google/gwt/emultest/java/lang/ByteTest.java b/user/test/com/google/gwt/emultest/java/lang/ByteTest.java index 8be3fa5dc77..2876ca069ab 100644 --- a/user/test/com/google/gwt/emultest/java/lang/ByteTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/ByteTest.java @@ -22,6 +22,19 @@ */ public class ByteTest extends GWTTestCase { + private static final int[] UNSIGNED_INTS = { + 0, + 1, + 2, + 3, + 0x12, + 0x5a, + 0x6c, + 0xfd, + 0xfe, + 0xff + }; + @Override public String getModuleName() { return "com.google.gwt.emultest.EmulSuite"; @@ -46,4 +59,16 @@ public void testStatics() { assertEquals(-128, Byte.valueOf((byte) -128).intValue()); assertEquals(-1, Byte.valueOf((byte) 255).intValue()); } + + public void testToUnsignedInt() { + for (int a : UNSIGNED_INTS) { + assertEquals(a, Byte.toUnsignedInt((byte) a)); + } + } + + public void testToUnsignedLong() { + for (int a : UNSIGNED_INTS) { + assertEquals((long) a, Byte.toUnsignedLong((byte) a)); + } + } } diff --git a/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java b/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java index 4f93f55aa99..0e0f13f27fb 100644 --- a/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java @@ -23,6 +23,20 @@ */ public class IntegerTest extends GWTTestCase { + private static final long[] UNSIGNED_INTS = { + 0L, + 1L, + 2L, + 3L, + 0x12345678L, + 0x5a4316b8L, + 0x6cf78a4bL, + 0xff1a618bL, + 0xfffffffdL, + 0xfffffffeL, + 0xffffffffL + }; + @Override public String getModuleName() { return "com.google.gwt.emultest.EmulSuite"; @@ -128,6 +142,16 @@ public void testCompareTo() { assertEquals(0, new Integer("12345").compareTo(new Integer(12345))); } + public void testCompareUnsigned() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + int cmpAsLongs = Long.compare(a, b); + int cmpAsUInt = Integer.compareUnsigned((int) a, (int) b); + assertEquals(Integer.signum(cmpAsLongs), Integer.signum(cmpAsUInt)); + } + } + } + public void testConstants() { assertEquals(32, Integer.SIZE); assertEquals(0x7fffffff, Integer.MAX_VALUE); @@ -159,6 +183,19 @@ public void testDecode() { } } + public void testDivideUnsigned() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + try { + assertEquals((int) (a / b), Integer.divideUnsigned((int) a, (int) b)); + assertFalse(b == 0); + } catch (ArithmeticException e) { + assertEquals(0, b); + } + } + } + } + public void testEquals() { assertFalse(new Integer(12345).equals(new Integer(12346))); assertEquals(new Integer("12345"), new Integer(12345)); @@ -206,6 +243,87 @@ public void testNumberOfTrailingZeros() { assertEquals(4, Integer.numberOfTrailingZeros(-0x7ff0)); } + public void testParseUnsignedInt() { + for (long a : UNSIGNED_INTS) { + assertEquals((int) a, Integer.parseUnsignedInt(Long.toString(a))); + } + } + + public void testParseUnsignedIntFail() { + try { + Integer.parseUnsignedInt(Long.toString(1L << 32)); + fail(); + } catch (NumberFormatException expected) { + } + + try { + Integer.parseUnsignedInt("-1"); + fail(); + } catch (NumberFormatException expected) { + } + } + + public void testParseUnsignedIntWithRadix() { + for (long a : UNSIGNED_INTS) { + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + assertEquals((int) a, Integer.parseUnsignedInt(Long.toString(a, radix), radix)); + } + } + } + + public void testParseUnsignedIntWithRadixLimits() { + // loops through all legal radix values. + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + // tests can successfully parse a number string with this radix. + String maxAsString = Long.toString((1L << 32) - 1, radix); + assertEquals(-1, Integer.parseUnsignedInt(maxAsString, radix)); + + try { + // tests that we get exception where an overflow would occur. + long overflow = 1L << 32; + String overflowAsString = Long.toString(overflow, radix); + Integer.parseUnsignedInt(overflowAsString, radix); + fail(); + } catch (NumberFormatException expected) { + } + } + } + + public void testParseUnsignedIntThrowsExceptionForInvalidRadix() { + // Valid radix values are Character.MIN_RADIX to Character.MAX_RADIX, inclusive. + try { + Integer.parseUnsignedInt("0", Character.MIN_RADIX - 1); + fail(); + } catch (NumberFormatException expected) { + } + + try { + Integer.parseUnsignedInt("0", Character.MAX_RADIX + 1); + fail(); + } catch (NumberFormatException expected) { + } + + // The radix is used as an array index, so try a negative value. + try { + Integer.parseUnsignedInt("0", -1); + fail(); + } catch (NumberFormatException expected) { + } + } + + public void testRemainderUnsigned() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + try { + assertEquals((int) (a % b), Integer.remainderUnsigned((int) a, (int) b)); + assertFalse(b == 0); + } catch (ArithmeticException e) { + assertEquals(0, b); + } + } + } + } + public void testReverse() { assertEquals(0, Integer.reverse(0)); assertEquals(-1, Integer.reverse(-1)); @@ -259,6 +377,8 @@ public void testStaticValueOf() { } public void testToBinaryString() { + assertEquals("0", Integer.toBinaryString(0)); + assertEquals("11111111111111111111111111111111", Integer.toBinaryString(-1)); assertEquals("1111111111111111111111111111111", Integer.toBinaryString(Integer.MAX_VALUE)); assertEquals("10000000000000000000000000000000", Integer.toBinaryString(Integer.MIN_VALUE)); } @@ -266,9 +386,10 @@ public void testToBinaryString() { public void testToHexString() { assertEquals("12345", Integer.toHexString(0x12345)); assertEquals("fff12345", Integer.toHexString(0xFFF12345)); + assertEquals("0", Integer.toHexString(0)); + assertEquals("ffffffff", Integer.toHexString(-1)); assertEquals("7fffffff", Integer.toHexString(Integer.MAX_VALUE)); assertEquals("80000000", Integer.toHexString(Integer.MIN_VALUE)); - assertEquals("ffffffff", Integer.toHexString(-1)); String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; @@ -278,6 +399,8 @@ public void testToHexString() { } public void testToOctalString() { + assertEquals("0", Integer.toOctalString(0)); + assertEquals("37777777777", Integer.toOctalString(-1)); assertEquals("17777777777", Integer.toOctalString(Integer.MAX_VALUE)); assertEquals("20000000000", Integer.toOctalString(Integer.MIN_VALUE)); } @@ -291,6 +414,7 @@ public void testToString() { assertEquals("-2147483647", Integer.toString(-Integer.MAX_VALUE)); assertEquals("-2147483648", Integer.toString(Integer.MIN_VALUE)); assertEquals("0", Integer.toString(0)); + assertEquals("-1", Integer.toString(-1)); assertEquals("7fffffff", Integer.toString(Integer.MAX_VALUE, 16)); assertEquals("2147483647", Integer.toString(Integer.MAX_VALUE, 10)); @@ -308,6 +432,21 @@ public void testToString() { assertEquals("-10000000000000000000000000000000", Integer.toString(Integer.MIN_VALUE, 2)); } + public void testToUnsignedLong() { + for (long a : UNSIGNED_INTS) { + assertEquals(a, Integer.toUnsignedLong((int) a)); + } + } + + public void testToUnsignedString() { + int[] bases = {2, 5, 7, 8, 10, 16}; + for (long a : UNSIGNED_INTS) { + for (int base : bases) { + assertEquals(Integer.toUnsignedString((int) a, base), Long.toString(a, base)); + } + } + } + public void testValueOf() { assertEquals(new Integer(12345), Integer.valueOf("12345")); assertEquals(new Integer(1865), Integer.valueOf("12345", 6)); diff --git a/user/test/com/google/gwt/emultest/java/lang/LongTest.java b/user/test/com/google/gwt/emultest/java/lang/LongTest.java index 37fb27d6346..f37229b2aa1 100644 --- a/user/test/com/google/gwt/emultest/java/lang/LongTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/LongTest.java @@ -17,6 +17,8 @@ import com.google.gwt.junit.client.GWTTestCase; +import java.math.BigInteger; + /** * Unit tests for the Javascript emulation of the Long/long autoboxed * fundamental type. @@ -43,12 +45,43 @@ public void testCompare() { assertEquals(-1, Long.compare(Long.MIN_VALUE, 1L)); } + public void testCompareUnsigned() { + // max value + assertTrue(Long.compareUnsigned(0, 0xffffffffffffffffL) < 0); + assertTrue(Long.compareUnsigned(0xffffffffffffffffL, 0) > 0); + + // both with high bit set + assertTrue(Long.compareUnsigned(0xff1a618b7f65ea12L, 0xffffffffffffffffL) < 0); + assertTrue(Long.compareUnsigned(0xffffffffffffffffL, 0xff1a618b7f65ea12L) > 0); + + // one with high bit set + assertTrue(Long.compareUnsigned(0x5a4316b8c153ac4dL, 0xff1a618b7f65ea12L) < 0); + assertTrue(Long.compareUnsigned(0xff1a618b7f65ea12L, 0x5a4316b8c153ac4dL) > 0); + + // neither with high bit set + assertTrue(Long.compareUnsigned(0x5a4316b8c153ac4dL, 0x6cf78a4b139a4e2aL) < 0); + assertTrue(Long.compareUnsigned(0x6cf78a4b139a4e2aL, 0x5a4316b8c153ac4dL) > 0); + + // same value + assertTrue(Long.compareUnsigned(0xff1a618b7f65ea12L, 0xff1a618b7f65ea12L) == 0); + } + public void testConstants() { assertEquals(64, Long.SIZE); assertEquals(0x7fffffffffffffffL, Long.MAX_VALUE); assertEquals(0x8000000000000000L, Long.MIN_VALUE); } + public void testDivideUnsigned() { + assertEquals(2, Long.divideUnsigned(14, 5)); + assertEquals(0, Long.divideUnsigned(0, 50)); + assertEquals(1, Long.divideUnsigned(0xfffffffffffffffeL, 0xfffffffffffffffdL)); + assertEquals(0, Long.divideUnsigned(0xfffffffffffffffdL, 0xfffffffffffffffeL)); + assertEquals(281479271743488L, Long.divideUnsigned(0xfffffffffffffffeL, 65535)); + assertEquals(0x7fffffffffffffffL, Long.divideUnsigned(0xfffffffffffffffeL, 2)); + assertEquals(3689348814741910322L, Long.divideUnsigned(0xfffffffffffffffeL, 5)); + } + public void testHighestOneBit() { assertEquals(0, Long.highestOneBit(0)); assertEquals(Long.MIN_VALUE, Long.highestOneBit(-1)); @@ -196,6 +229,91 @@ public void testParse() { } } + public void testParseUnsignedLong() { + assertEquals(0xffffffffffffffffL, Long.parseUnsignedLong("18446744073709551615")); + assertEquals(0x7fffffffffffffffL, Long.parseUnsignedLong("9223372036854775807")); + assertEquals(0xff1a618b7f65ea12L, Long.parseUnsignedLong("18382112080831834642")); + assertEquals(0x5a4316b8c153ac4dL, Long.parseUnsignedLong("6504067269626408013")); + assertEquals(0x6cf78a4b139a4e2aL, Long.parseUnsignedLong("7851896530399809066")); + } + + public void testParseUnsignedLongFails() { + try { + Long.parseUnsignedLong("-1"); + fail(); + } catch (NumberFormatException expected) { + } + + try { + // One more than maximum value + Long.parseUnsignedLong("18446744073709551616"); + fail(); + } catch (NumberFormatException expected) { + } + } + + public void testParseUnsignedLongWithRadix() { + assertEquals(0xffffffffffffffffL, Long.parseUnsignedLong("ffffffffffffffff", 16)); + assertEquals(0x1234567890abcdefL, Long.parseUnsignedLong("1234567890abcdef", 16)); + } + + public void testParseUnsignedLongWithRadixLimits() { + BigInteger max = BigInteger.ZERO.setBit(64).subtract(BigInteger.ONE); + // loops through all legal radix values. + for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) { + // tests can successfully parse a number string with this radix. + String maxAsString = max.toString(radix); + assertEquals(max.longValue(), Long.parseUnsignedLong(maxAsString, radix)); + + try { + // tests that we get exception where an overflow would occur. + BigInteger overflow = max.add(BigInteger.ONE); + String overflowAsString = overflow.toString(radix); + Long.parseUnsignedLong(overflowAsString, radix); + fail(); + } catch (NumberFormatException expected) { + } + } + + try { + Long.parseUnsignedLong("1234567890abcdef1", 16); + } catch (NumberFormatException expected) { + } + } + + public void testParseUnsignedLongThrowsExceptionForInvalidRadix() { + // Valid radix values are Character.MIN_RADIX to Character.MAX_RADIX, inclusive. + try { + Long.parseUnsignedLong("0", Character.MIN_RADIX - 1); + fail(); + } catch (NumberFormatException expected) { + } + + try { + Long.parseUnsignedLong("0", Character.MAX_RADIX + 1); + fail(); + } catch (NumberFormatException expected) { + } + + // The radix is used as an array index, so try a negative value. + try { + Long.parseUnsignedLong("0", -1); + fail(); + } catch (NumberFormatException expected) { + } + } + + public void testRemainderUnsigned() { + assertEquals(4, Long.remainderUnsigned(14, 5)); + assertEquals(0, Long.remainderUnsigned(0, 50)); + assertEquals(1, Long.remainderUnsigned(0xfffffffffffffffeL, 0xfffffffffffffffdL)); + assertEquals(0xfffffffffffffffdL, + Long.remainderUnsigned(0xfffffffffffffffdL, 0xfffffffffffffffeL)); + assertEquals(65534L, Long.remainderUnsigned(0xfffffffffffffffeL, 65535)); + assertEquals(0, Long.remainderUnsigned(0xfffffffffffffffeL, 2)); + assertEquals(4, Long.remainderUnsigned(0xfffffffffffffffeL, 5)); + } + public void testReverse() { assertEquals(0L, Long.reverse(0L)); assertEquals(-1L, Long.reverse(-1L)); @@ -248,6 +366,12 @@ public void testStaticValueOf() { public void testToBinaryString() { assertEquals("0", Long.toBinaryString(0L)); + assertEquals("1111111111111111111111111111111111111111111111111111111111111111", + Long.toBinaryString(-1)); + assertEquals("1000000000000000000000000000000000000000000000000000000000000000", + Long.toBinaryString(Long.MIN_VALUE)); + assertEquals("111111111111111111111111111111111111111111111111111111111111111", + Long.toBinaryString(Long.MAX_VALUE)); assertEquals("10001111101101101111011110001100100000000", Long.toBinaryString(1234500000000L)); assertEquals("1111111111111111111111101110000010010010000100001110011100000000", Long.toBinaryString(-1234500000000L)); @@ -272,11 +396,16 @@ public void testToHexString() { assertEquals("ffffffffffffffff", Long.toHexString(-1)); assertEquals("80000000", Long.toHexString(((long) Integer.MAX_VALUE) + 1)); assertEquals("ffffffff", Long.toHexString(4294967295L)); + assertEquals("0", Long.toHexString(0)); + assertEquals("8000000000000000", Long.toHexString(Long.MIN_VALUE)); + assertEquals("7fffffffffffffff", Long.toHexString(Long.MAX_VALUE)); } public void testToOctalString() { assertEquals("7", Long.toOctalString(7L)); assertEquals("77777777777", Long.toOctalString(077777777777L)); + assertEquals("0", Long.toOctalString(0)); + assertEquals("1777777777777777777777", Long.toOctalString(-1)); assertEquals("1000000000000000000000", Long.toOctalString(Long.MIN_VALUE)); assertEquals("777777777777777777777", Long.toOctalString(Long.MAX_VALUE)); assertEquals("1777777777777777777777", Long.toOctalString(-1)); @@ -286,6 +415,8 @@ public void testToOctalString() { public void testToString() { assertEquals("89000000005", new Long(89000000005L).toString()); + assertEquals("0", new Long(0).toString()); + assertEquals("-1", new Long(-1).toString()); assertEquals("-9223372036854775808", new Long(Long.MIN_VALUE).toString()); assertEquals("9223372036854775807", new Long(Long.MAX_VALUE).toString()); @@ -310,4 +441,22 @@ public void testToString() { assertEquals("-8000000000000000", Long.toString(0x8000000000000000L, 16)); assertEquals("7fffffffffffffff", Long.toString(0x7fffffffffffffffL, 16)); } + + public void testToUnsignedString() { + String[] tests = { + "ffffffffffffffff", + "7fffffffffffffff", + "ff1a618b7f65ea12", + "5a4316b8c153ac4d", + "6cf78a4b139a4e2a" + }; + int[] bases = { 2, 5, 7, 8, 10, 16 }; + for (int base : bases) { + for (String x : tests) { + BigInteger xValue = new BigInteger(x, 16); + long xLong = xValue.longValue(); // signed + assertEquals(xValue.toString(base), Long.toUnsignedString(xLong, base)); + } + } + } } diff --git a/user/test/com/google/gwt/emultest/java/lang/ShortTest.java b/user/test/com/google/gwt/emultest/java/lang/ShortTest.java index 3a305ff4038..54264f422e2 100644 --- a/user/test/com/google/gwt/emultest/java/lang/ShortTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/ShortTest.java @@ -22,6 +22,20 @@ */ public class ShortTest extends GWTTestCase { + private static final int[] UNSIGNED_INTS = { + 0, + 1, + 2, + 3, + 0x1234, + 0x5a43, + 0x6cf7, + 0xff1a, + 0xfffd, + 0xfffe, + 0xffff + }; + @Override public String getModuleName() { return "com.google.gwt.emultest.EmulSuite"; @@ -48,4 +62,16 @@ public void testStaticValueOf() { assertEquals(Short.MIN_VALUE, Short.valueOf(Short.MIN_VALUE).shortValue()); assertEquals(Short.MAX_VALUE, Short.valueOf(Short.MAX_VALUE).shortValue()); } + + public void testToUnsignedInt() { + for (int a : UNSIGNED_INTS) { + assertEquals(a, Short.toUnsignedInt((short) a)); + } + } + + public void testToUnsignedLong() { + for (int a : UNSIGNED_INTS) { + assertEquals((long) a, Short.toUnsignedLong((short) a)); + } + } }