Desugar Integer.{divide,remainder}Unsigned and {Integer,Long}.toUnsignedString
Note Long.{divide,remainder}Unsigned are already desugared
PiperOrigin-RevId: 335511222
diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD b/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD
index d589693..b5a53f1 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD
+++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD
@@ -96,6 +96,24 @@
],
)
+java_test(
+ name = "UnsignedIntsTest",
+ srcs = ["UnsignedIntsTest.java"],
+ deps = [
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:primitives",
+ "//third_party:junit4",
+ ],
+)
+
+java_test(
+ name = "UnsignedLongsTest",
+ srcs = ["UnsignedLongsTest.java"],
+ deps = [
+ "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:primitives",
+ "//third_party:junit4",
+ ],
+)
+
test_suite(
name = "windows_tests",
tags = [
diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java
new file mode 100644
index 0000000..51f6967
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java
@@ -0,0 +1,113 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.android.desugar.runtime;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link UnsignedInts}. */
+@RunWith(JUnit4.class)
+public class UnsignedIntsTest {
+ private static final long[] UNSIGNED_INTS = {
+ 0L,
+ 1L,
+ 2L,
+ 3L,
+ 0x12345678L,
+ 0x5a4316b8L,
+ 0x6cf78a4bL,
+ 0xff1a618bL,
+ 0xfffffffdL,
+ 0xfffffffeL,
+ 0xffffffffL
+ };
+
+ @Test
+ public void testToLong() {
+ for (long a : UNSIGNED_INTS) {
+ assertEquals(a, UnsignedInts.toLong((int) a));
+ }
+ }
+
+ @Test
+ public void testCompare() {
+ for (long a : UNSIGNED_INTS) {
+ for (long b : UNSIGNED_INTS) {
+ int cmpAsLongs = Long.compare(a, b);
+ int cmpAsUInt = UnsignedInts.compare((int) a, (int) b);
+ assertEquals(Integer.signum(cmpAsLongs), Integer.signum(cmpAsUInt));
+ }
+ }
+ }
+
+ @Test
+ public void testDivide() {
+ for (long a : UNSIGNED_INTS) {
+ for (long b : UNSIGNED_INTS) {
+ try {
+ assertEquals((int) (a / b), UnsignedInts.divide((int) a, (int) b));
+ assertFalse(b == 0);
+ } catch (ArithmeticException e) {
+ assertEquals(0, b);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testRemainder() {
+ for (long a : UNSIGNED_INTS) {
+ for (long b : UNSIGNED_INTS) {
+ try {
+ assertEquals((int) (a % b), UnsignedInts.remainder((int) a, (int) b));
+ assertFalse(b == 0);
+ } catch (ArithmeticException e) {
+ assertEquals(0, b);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testDivideRemainderEuclideanProperty() {
+ // Use a seed so that the test is deterministic:
+ Random r = new Random(0L);
+ for (int i = 0; i < 1000000; i++) {
+ int dividend = r.nextInt();
+ int divisor = r.nextInt();
+ // Test that the Euclidean property is preserved:
+ assertEquals(
+ 0,
+ dividend
+ - (divisor * UnsignedInts.divide(dividend, divisor)
+ + UnsignedInts.remainder(dividend, divisor)));
+ }
+ }
+
+ @Test
+ public void testToString() {
+ int[] bases = {2, 5, 7, 8, 10, 16};
+ for (long a : UNSIGNED_INTS) {
+ for (int base : bases) {
+ assertEquals(UnsignedInts.toString((int) a, base), Long.toString(a, base));
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java
index ec2d47a..235a1fd 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java
+++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java
@@ -17,6 +17,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import java.math.BigInteger;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,4 +88,24 @@
+ UnsignedLongs.remainderUnsigned(dividend, divisor)));
}
}
+
+ @Test
+ public void testToString() {
+ String[] tests = {
+ "0",
+ "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), UnsignedLongs.toString(xLong, base));
+ }
+ }
+ }
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto
index 2a14734..f0fb9a7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto
@@ -181,6 +181,95 @@
source {
opcode: 184
method_id {
+ owner: "java/lang/Integer"
+ name: "divideUnsigned"
+ desc: "(II)I"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts"
+ name: "divide"
+ desc: "(II)I"
+ }
+ is_interface: false
+ }
+ # TODO(deltazulu): adjust flag name to include INT_UNSIGNED
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
+replacements {
+ source {
+ opcode: 184
+ method_id {
+ owner: "java/lang/Integer"
+ name: "remainderUnsigned"
+ desc: "(II)I"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts"
+ name: "remainder"
+ desc: "(II)I"
+ }
+ is_interface: false
+ }
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
+replacements {
+ source {
+ opcode: 184
+ method_id {
+ owner: "java/lang/Integer"
+ name: "toUnsignedString"
+ desc: "(I)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts"
+ name: "toString"
+ desc: "(I)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
+replacements {
+ source {
+ opcode: 184
+ method_id {
+ owner: "java/lang/Integer"
+ name: "toUnsignedString"
+ desc: "(II)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts"
+ name: "toString"
+ desc: "(II)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
+replacements {
+ source {
+ opcode: 184
+ method_id {
owner: "java/lang/Long"
name: "divideUnsigned"
desc: "(JJ)J"
@@ -221,6 +310,50 @@
range: REPLACE_CALLS_TO_LONG_UNSIGNED
}
+replacements {
+ source {
+ opcode: 184
+ method_id {
+ owner: "java/lang/Long"
+ name: "toUnsignedString"
+ desc: "(J)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs"
+ name: "toString"
+ desc: "(J)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
+replacements {
+ source {
+ opcode: 184
+ method_id {
+ owner: "java/lang/Long"
+ name: "toUnsignedString"
+ desc: "(JI)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ destination {
+ opcode: 184
+ method_id {
+ owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs"
+ name: "toString"
+ desc: "(JI)Ljava/lang/String;"
+ }
+ is_interface: false
+ }
+ range: REPLACE_CALLS_TO_LONG_UNSIGNED
+}
+
# ==== DESUGAR_JAVA8_LIBS ====
replacements {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD
index bf1f779..5c6bafd 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD
@@ -23,6 +23,7 @@
name = "primitives",
srcs = [
"PrimitiveHashcode.java",
+ "UnsignedInts.java",
"UnsignedLongs.java",
],
# This library must be compiled with java7, as we directly copy it to the desugared jar.
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java
new file mode 100644
index 0000000..05cc101
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java
@@ -0,0 +1,102 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.android.desugar.runtime;
+
+/**
+ * Static utility methods pertaining to {@code int} primitives that interpret values as
+ * <i>unsigned</i> (that is, any negative value {@code x} is treated as the positive value {@code
+ * 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well as
+ * signed versions of methods for which signedness is an issue.
+ *
+ * <p>See the Guava User Guide article on <a
+ * href="https://github.com/google/guava/wiki/PrimitivesExplained#unsigned-support">unsigned
+ * primitive utilities</a>.
+ */
+public final class UnsignedInts {
+ static final long INT_MASK = 0xffffffffL;
+
+ private UnsignedInts() {}
+
+ private static int flip(int value) {
+ return value ^ Integer.MIN_VALUE;
+ }
+
+ /**
+ * Compares the two specified {@code int} values, treating them as unsigned values between {@code
+ * 0} and {@code 2^32 - 1} inclusive.
+ *
+ * @param a the first unsigned {@code int} to compare
+ * @param b the second unsigned {@code int} to compare
+ * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is
+ * greater than {@code b}; or zero if they are equal
+ */
+ /*visible for testing*/ static int compare(int a, int b) {
+ a = flip(a);
+ b = flip(b);
+ return (a < b) ? -1 : ((a > b) ? 1 : 0);
+ }
+
+ /**
+ * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned.
+ */
+ /*visible for testing*/ static long toLong(int value) {
+ return value & INT_MASK;
+ }
+
+ /**
+ * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit
+ * quantities.
+ *
+ * @param dividend the dividend (numerator)
+ * @param divisor the divisor (denominator)
+ * @throws ArithmeticException if divisor is 0
+ */
+ public static int divide(int dividend, int divisor) {
+ return (int) (toLong(dividend) / toLong(divisor));
+ }
+
+ /**
+ * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit
+ * quantities.
+ *
+ * @param dividend the dividend (numerator)
+ * @param divisor the divisor (denominator)
+ * @throws ArithmeticException if divisor is 0
+ */
+ public static int remainder(int dividend, int divisor) {
+ return (int) (toLong(dividend) % toLong(divisor));
+ }
+
+ /**
+ * Returns a string representation of x, where x is treated as unsigned.
+ */
+ public static String toString(int x) {
+ return toString(x, 10);
+ }
+
+ /**
+ * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as
+ * unsigned.
+ *
+ * @param x the value to convert to a string.
+ * @param radix the radix to use while working with {@code x}
+ * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX}
+ * and {@link Character#MAX_RADIX}.
+ */
+ public static String toString(int x, int radix) {
+ long asLong = x & INT_MASK;
+ return Long.toString(asLong, radix);
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java
index e142bb2..e5dde34 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java
@@ -39,8 +39,6 @@
* Compares the two specified {@code long} values, treating them as unsigned values between {@code
* 0} and {@code 2^64 - 1} inclusive.
*
- * <p><b>Java 8 users:</b> use {@link Long#compareUnsigned(long, long)} instead.
- *
* @param a the first unsigned {@code long} to compare
* @param b the second unsigned {@code long} to compare
* @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is
@@ -118,4 +116,63 @@
long rem = dividend - quotient * divisor;
return rem - (compare(rem, divisor) >= 0 ? divisor : 0);
}
+
+ /** Returns a string representation of x, where x is treated as unsigned. */
+ public static String toString(long x) {
+ return toString(x, 10);
+ }
+
+ /**
+ * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as
+ * unsigned.
+ *
+ * @param x the value to convert to a string.
+ * @param radix the radix to use while working with {@code x}
+ * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX}
+ * and {@link Character#MAX_RADIX}.
+ */
+ public static String toString(long x, int radix) {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ throw new IllegalArgumentException(
+ "radix (" + radix + ") must be between Character.MIN_RADIX and Character.MAX_RADIX");
+ }
+ if (x == 0) {
+ // Simply return "0"
+ return "0";
+ } else if (x > 0) {
+ return Long.toString(x, radix);
+ } else {
+ char[] buf = new char[64];
+ int i = buf.length;
+ if ((radix & (radix - 1)) == 0) {
+ // Radix is a power of two so we can avoid division.
+ int shift = Integer.numberOfTrailingZeros(radix);
+ int mask = radix - 1;
+ do {
+ buf[--i] = Character.forDigit(((int) x) & mask, radix);
+ x >>>= shift;
+ } while (x != 0);
+ } else {
+ // Separate off the last digit using unsigned division. That will leave
+ // a number that is nonnegative as a signed integer.
+ long quotient;
+ if ((radix & 1) == 0) {
+ // Fast path for the usual case where the radix is even.
+ quotient = (x >>> 1) / (radix >>> 1);
+ } else {
+ quotient = divideUnsigned(x, radix);
+ }
+ long rem = x - quotient * radix;
+ buf[--i] = Character.forDigit((int) rem, radix);
+ x = quotient;
+ // Simple modulo/division approach
+ while (x > 0) {
+ buf[--i] = Character.forDigit((int) (x % radix), radix);
+ x /= radix;
+ }
+ }
+ // Generate string
+ return new String(buf, i, buf.length - i);
+ }
+ }
}