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);
+    }
+  }
 }