- Compact Profiles
- Unsigned Integer API
- Integer Arithmetic Overflow/Underflow Detection API
Unsigned Integer API
Java has been criticized for not supporting unsigned integers. Instead, its byte, short, int, and long types describe signed integers whose values are stored in two's complement form.
Unsigned integers are useful when working with binary values (such as representing memory contents, bit masking, or bit shifting), and in various numeric-processing contexts (such as cryptography). They're also a form of self-documentation for values that are never negative; for example, a counter or an array's length.
Java's lack of support for unsigned integers often leads to a tedious translation process when translating to Java from C language–based source code that uses unsigned integers. Care is required to ensure that the equivalent Java source code behaves identically to its C counterpart.
To address Java's lack of support for unsigned integers, developers often use larger data types; for example, storing a 16-bit unsigned value in a 32-bit signed integer or a 32-bit unsigned value in a 64-bit signed integer. However, there's no way to record a 64-bit unsigned integer, because Java doesn't offer a 128-bit signed integer type. Also, this approach requires twice the memory to store the unsigned values, and any logic that depends on the rules of two's-complement overflow must often be rewritten.
Java's signed integer types can emulate same-sized unsigned integers. Java SE 8 complements this emulation by adding new static methods to java.lang.Integer and java.lang.Long for converting between strings and unsigned integers, comparing two signed integer values as unsigned integers, and performing unsigned division and remainder operations. (Addition, subtraction, and multiplication methods aren't needed because +, -, and * properly handle signed or unsigned operands in twos-complement arithmetic.)
Java SE 8 doesn't introduce new unsigned types (such as UnsignedInteger) with methods that perform arithmetic on unsigned integers to avoid dealing with the overhead of autoboxing and unboxing, and to allow the built-in arithmetic operators to be reused.
Consider Integer, which offers the following unsigned integer methods (Long offers a similar complement of methods):
- public static int compareUnsigned(int x, int y): Compare x and y as two unsigned values. Return 0 when they are equal, a value less than 0 when x is less than y, and a value greater than 0 when x is greater than y.
- public static int divideUnsigned(int dividend, int divisor): Return the unsigned quotient of dividing dividend by divisor, where each argument and the result are interpreted as unsigned values.
- public static int parseUnsignedInt(String s): Parse s as an unsigned decimal integer by invoking parseUnsignedInt(String, int) with s and a radix (the base of a system of numeration) value of 10 as arguments.
- public static int parseUnsignedInt(String s, int radix): Parse s as an unsigned integer according to radix. The characters must all be digits of the specified radix. For example, when 16 is passed to radix, the characters are 0 through 9, A through F, and a through f. The first character may be an ASCII plus sign (+). Throw java.lang.NumberFormatException when null is passed to s, the length of s is zero, the value of radix is less than java.lang.Character.MIN_RADIX or larger than Character.MAX_RADIX, a character is detected that isn't a plus sign (first character only) or a digit according to radix, or the string's value is larger than the largest unsigned int, which happens to be 2^{32}–1.
- static int remainderUnsigned(int dividend, int divisor): Return the unsigned remainder of dividing dividend by divisor, where each argument and the result are interpreted as unsigned values.
- public static long toUnsignedLong(int x): Convert x to a long by an unsigned conversion, in which the high-order 32 bits of the long are set to 0 and the low-order 32 bits are equal to the bits of x.
- public static String toUnsignedString(int i): Return a string representation of i as an unsigned decimal value. The argument is converted to unsigned decimal representation and returned as a string exactly as if the argument and a radix value of 10 were given as arguments to the toUnsignedString(int, int) method.
- public static String toUnsignedString(int i, int radix): Return a string representation of i as an unsigned integer value in the specified radix. A radix value of 10 is assumed when radix is less than Character.MIN_RADIX or greater than Character.MAX_RADIX.
Java SE 8 doesn't introduce most of the aforementioned methods to java.lang.Byte and java.lang.Short because these types aren't arithmetically supported by the virtual machine—bytes and shorts are promoted to ints before arithmetic operations are performed on them. However, Java SE 8 adds toUnsignedInt() and toUnsignedLong() methods to these classes so that you can conveniently convert bytes and shorts to unsigned ints and longs.
Listing 2 demonstrates some of the Integer class's unsigned integer methods.
Listing 2: UIDemo.java.
public class UIDemo { public static void main(String[] args) { int x = Integer.MAX_VALUE; int y = Integer.MAX_VALUE+1; System.out.printf("%d %d%n", x, y); System.out.printf("x compared to y: %d%n", Integer.compare(x, y)); System.out.printf("x compared to y: %d%n", Integer.compareUnsigned(x, y)); System.out.printf("y divided by x: %d%n", y/x); System.out.printf("y divided by x: %d%n", Integer.divideUnsigned(y, x)); System.out.printf("x+y: %s%n", Integer.toString(x+y)); System.out.printf("x+y: %s%n", Integer.toUnsignedString(x+y)); System.out.printf("parse(\"2147483647\"): %d%n", Integer.parseUnsignedInt("2147483647")); System.out.printf("parse(\"2147483648\"): %d%n", Integer.parseUnsignedInt("2147483648")); System.out.printf("parse(\"-2147483648\"): %d%n", Integer.parseUnsignedInt("-2147483648")); } }
Compile Listing 2 (javac UIDemo.java) and run this application (java UIDemo). You should observe the following output (slightly reformatted for readability):
2147483647 -2147483648 x compared to y: 1 x compared to y: -1 y divided by x: -1 y divided by x: 1 x+y: -1 x+y: 4294967295 parse("2147483647"): 2147483647 parse("2147483648"): -2147483648 Exception in thread "main" java.lang.NumberFormatException: Illegal leading minus sign on unsigned string -2147483648. at java.lang.Integer.parseUnsignedInt(Integer.java:672) at java.lang.Integer.parseUnsignedInt(Integer.java:711) at UIDemo.main(UIDemo.java:16)
The first output line reveals the largest and smallest 32-bit integer values. The second and third output lines reveal that x is greater than y in a signed context and less than y in an unsigned context. The fourth and fifth lines show the results of signed and unsigned division, and the subsequent sixth and seventh lines show string conversion treating the addition result as signed or unsigned.
When you parse an unsigned integer, the string can represent any value from 0 through MAX_VALUE*2+1. However, as the eighth output line shows, the parsed result of an integer greater than MAX_VALUE outputs as a negative value. Because parsedUnsignedInt() cannot parse negative values, the final output lines reveal a thrown NumberFormatException.