- Unsigned int in Java
- An unsigned int
- Printing an unsigned int
- Converting from long to unsigned int
- Converting from unsigned int to long
- Comparing unsigned int s
- Parsing an unsigned int
- Arithmetics
- Беззнаковая арифметика в Java
- Преобразование byte в short (int, long)
- Сравнение без учёта знака
- Сложение, вычитание и умножение
- Деление
- Побитовые сдвиги
- Заключительные рекомендации
- Create Unsigned Long in Java
- Create Unsigned Long Integer Using BigInteger in Java
- Unsigned Long Using ULong of the jOOU
- Related Article — Java Long
- Related Article — Java BigInteger
Unsigned int in Java
Java does not have unsigned data types. Your options are:
- Use a long
- Use an UnsignedInteger from Guava
- Use an int and interpret the bits as unsigned (described below)
An unsigned int
An int is always signed in Java, but nothing prevents you from viewing an int simply as 32 bits and interpret those bits as a value between 0 and 2 64 .
Java int value | Bits | Interpreted as unsigned |
---|---|---|
0 | 00000000…00000000 | 0 |
1 | 00000000…00000001 | 1 |
⋮ | ⋮ | ⋮ |
2,147,483,647 | 01111111…11111111 | 2,147,483,647 |
−2,147,483,648 | 10000000…00000000 | 2,147,483,648 |
⋮ | ⋮ | ⋮ |
−2 | 11111111…11111110 | 4,294,967,294 |
−1 | 11111111…11111111 | 4,294,967,295 |
Keep in mind that there’s nothing you can do to force your interpretation upon someone else’s method. If a method accepts a int , then that method accepts a value between −2 31 and 2 31 − 1 unless explicitly stated otherwise.
Here are a couple of useful conversions / manipulations.
Printing an unsigned int
System.out.println("Value of my unsigned int: " + Integer.toUnsignedString(uint));
Converting from long to unsigned int
Casting to int throws away all but the lowest 32 bits.
long lng = 2200000000L; // 00000000 . 00000000 10000011 00100001 01010110 00000000 int i = (int) lng; // 10000011 00100001 01010110 00000000 System.out.println(i); // -2094967296 System.out.println(Integer.toUnsignedString(i)); // 2200000000
Converting from unsigned int to long
int i = 0b10000011001000010101011000000000; // -2094967296 or 2200000000 long signed = i; // -2094967296 (with sign extension) long unsigned = Integer.toUnsignedLong(i); // 2200000000 (without sign extension)
Comparing unsigned int s
int cmp = Integer.compareUnsigned(i1, i2); // cmp = -1 => i1 < i2// cmp = 0 => i1 = i2 // cmp = 1 => 11 > i2
Parsing an unsigned int
int i = Integer.parseUnsignedInt("2200000000"); System.out.println(Integer.toUnsignedString(i)); // 2200000000
Note: Integer.parseInt would throw a NumberFormatException for the above input.
Arithmetics
As it happens, the 2-complement representation “just works” for addition, subtraction and multiplication.
// two unsigned ints int i1 = (int) 2200000000L; int i2 = 55555; int sum = i1 + i2; // 2200055555 int diff = i1 - i2; // 2199944445 int prod = i2 * i2; // 3086358025
int q = Integer.divideUnsigned(i1, i2); // 39600 int r = Integer.remainderUnsigned(i1, i2); // 22000
Беззнаковая арифметика в Java
Как известно, в Java нет беззнаковых типов. Если в Си вы могли написать unsigned int ( char , long ), то в Java так не получится. Однако нередко возникает необходимость в выполнении арифметических операций именно с числами без знака. На первый взгляд кажется, что беззнаковые типы в принципе-то и не особо нужны (подумаешь, MaxInt для чисел со знаком меньше в два раза, если нужны числа больше, я просто возьму long и далее BigInteger ). Но основное различие на самом деле не в том, сколько различных неотрицательных чисел можно положить в signed или unsigned int, а в том, как над ними производятся арифметические операции и сравнения. Если вы работаете с бинарными протоколами или с двоичной арифметикой, где важен каждый используемый бит, нужно уметь выполнять все основные операции в беззнаковом режиме. Рассмотрим эти операции по порядку:
Преобразование byte в short (int, long)
Обычный каст (int) myByte выполнит расширение до 32 бит со знаком — это означает, что если старший бит байта был установлен в 1, то результатом будет то же самое отрицательное число, но записанное в 32-битном формате:
Часто это не то, чего бы мы хотели. Для того, чтобы выполнить расширение до 32 бит без знака и получить 0x000000ff , в Java можно записать:
int myInt = myByte & 0xff; short myShort = myByte & 0xff;
Сравнение без учёта знака
Для беззнакового сравнения есть лаконичная формула:
int compareUnsigned(int a, int b)
Сложение, вычитание и умножение
А вот здесь приятный сюрприз — эти операции выполняются корректно в любом случае. Но в выражениях необходимо тщательно следить за тем, чтобы операции выполнялись с числами одного типа, так как любые неявные преобразования выполняются с расширением знака, и могут приводить к результатам, отличным от ожидаемых. Коварство таких багов в том, что ошибочный сценарий может выполняться очень редко.
Деление
Деление -256 на 256 даст нам -1. А нам бы хотелось, чтобы 0xffffff00 / 0x100 давало 0x00ffffff , а не 0xffffffff (-1) . Для byte , short и int решением будет переход к числам большей разрядности:
int a = 0xffffff00; int b = 0x100; int c = (int) ((a & 0xffffffffL) / b); // convert a to long before division
Но что делать с long ? Переходить на BigInteger в таких случаях обычно не вариант — слишком медленно. Остаётся только брать всё в свои руки и реализовывать деление вручную. К счастью, всё уже украдено до нас — в Google Guava есть реализация беззнакового деления для long , причём довольно шустрая. Если вы не используете эту библиотеку, проще всего выдрать кусок кода прямо из файла UnsignedLongs.java:
/** * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit * quantities. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) * @throws ArithmeticException if divisor is 0 */ public static long divide(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) = 0 ? 1 : 0); >
Чтобы код компилировался, придётся также позаимствовать реализацию compare(long, long) :
/** * Compares the two specified values, treating them as unsigned values between * and inclusive. * * @param a the first unsigned to compare * @param b the second unsigned to compare * @return a negative value if is less than ; a positive value if is * greater than ; or zero if they are equal */ public static int compare(long a, long b)
и Longs.compare(long, long) + flip(long) :
/** * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on * longs, that is, as unsigned longs if and only if * as signed longs. */ private static long flip(long a) < return a ^ Long.MIN_VALUE; >/** * Compares the two specified values. The sign of the value * returned is the same as that of . * * @param a the first to compare * @param b the second to compare * @return a negative value if is less than ; a positive * value if is greater than ; or zero if they are equal */ public static int compare(long a, long b) < return (a < b) ? -1 : ((a >b) ? 1 : 0); >
Побитовые сдвиги
Чтобы окончательно покрыть тему о битовых операциях, вспомним также о сдвигах. В x86 ассемблере есть целая пачка различных команд, которые делают побитовые сдвиги — SHL, SHR, SAL, SAR, ROR, ROL, RCR, RCL. Последние 4 осуществляют циклические сдвиги, их эквивалентов в Java нет. А вот логические и арифметические сдвиги присутствуют. Логический сдвиг (не учитывает знака) — SHL (shift left) и SHR (shift right) — реализуется в Java операторами >> соответственно. С помощью логических сдвигов можно быстро выполнять целочисленные умножение и деление на числа степени двойки. Арифметический сдвиг (учитывает знак) вправо — SAR — реализуется оператором >> . Арифметический сдвиг влево эквивалентен логическому, и поэтому специального оператора для него нет. Может показаться странным, что в ассемблере есть специальный опкод для этой операции, но на самом деле он делает то же самое, то есть SAL полностью повторяет поведение SHL, и об этом прямо говорит документация от Intel:
The shift arithmetic left (SAL) and shift logical left (SHL) instructions perform the same operation; they shift the bits in the destination operand to the left (toward more significant bit locations). For each shift count, the most significant bit of the destination operand is shifted into the CF flag, and the least significant bit is cleared (see Figure 7-7 in the Intel®64 and IA-32 Architectures Software Developer’sManual, Volume 1).
То есть SAL добавили просто для симметрии, с учётом того, что для сдвига вправо есть разделение на логический и арифметический. Ну а Гослинг решил не заморачиваться (и, думается, правильно сделал).
a > 1; // сдвиг вправо с учётом знака (эквивалентно делению на 2) a >>> 1; // сдвиг вправо без учёта знака (эквивалентно беззнаковому делению на 2)
Заключительные рекомендации
- При выполнении арифметических действий, которые могут привести к переполнению в выбранной разрядной сетке, нужно всегда точно представлять, какая область допустимых значений может быть у переменных, и отслеживать эти инварианты, расставляя утверждения (assertions). Например, очевидно, что при умножении двух произвольных 32-разрядных беззнаковых чисел результат может не поместиться в 32 бита, и если вам нужно избежать переполнения, нужно либо убедиться, что в этом месте никогда не будет ситуации, при которой произведение не влезает в 32 бита, либо необходимо предварительно сконвертировать оба операнда в long (выполнив a & 0xffffffffL ). Здесь, кстати, можно легко допустить ошибку, сконвертировав только один из операндов. Нет, нужно сконвертировать в long оба, т.к. если второй операнд окажется отрицательным, он будет неявно преобразован в long с расширением знака, и результат умножения будет неправильным.
- Щедро расставляйте скобки в выражениях, где используются побитовые операции. Дело в том, что приоритет побитовых операторов в Java несколько странный, и часто ведёт себя неочевидным образом. Лучше добавить пару скобок, чем потом несколько часов искать трудноуловимые ошибки.
- Если вам нужна константа типа long , не забудьте добавить суффикс L в конец литерала константы. Если этого не сделать, это будет не long , а int , и при неявном приведении к long снова произойдёт неприятное нам расширение со знаком.
Create Unsigned Long in Java
- Create Unsigned Long Integer Using BigInteger in Java
- Unsigned Long Using ULong of the jOOU
A long variable in Java is always signed, which always has negative and positive values. But we can also use and treat the general long variable as an unsigned variable using a few methods mentioned below.
Create Unsigned Long Integer Using BigInteger in Java
In Java, primitive types like long provide a vast range of big integer calculations.
Still, to go further than that range, we need to use the BigInteger class that we can use to perform mathematical operations on larger values than the primitive types can hold. As the unsigned long value is large, we use the BigInteger .
In this example, we first call the valueOf() function of the BigInteger class and pass Long.MAX_VALUE , the maximum value it can store. Then, we add the value that we want to store, which we get using BigInteger.valueOf() and pass the value.
To convert the BigInteger value to a long value we use BigInteger.longValue() and then we make the longValue as a Unsigned String by calling Long.toUnsignedString() and pass the longValue variable. We can see that the value is printed in the output, and it is a positive number which means that it is an unsigned long value.
import java.math.BigInteger; public class UnsignedLongExample public static void main(String[] args) BigInteger bigInteger = BigInteger.valueOf(Long.MAX_VALUE) .add(BigInteger.valueOf(25634)); long longValue = bigInteger.longValue(); String longAsUnsignedString = Long.toUnsignedString(longValue); System.out.println(longAsUnsignedString); > >
If we print the longValue directly, we get a negative value because long is still a signed variable but the function toUnsignedString() converts it to a string with the unsigned value.
System.out.println(longValue);
Unsigned Long Using ULong of the jOOU
Another way to get and use an unsigned long value is by using a third-party library called jOOU . We need to import the following maven dependency to use it.
The ULong class in org.jooq.tools.unsigned provides a few methods to convert a long value to an unsigned value. One of the methods is the valueOf() method that takes a long value.
In the program, we create a BigInteger and then in the ULong.valueOf() method, we pass the long value using bigInteger.longValue() that returns a ULong object.
import org.jooq.tools.unsigned.ULong; import java.math.BigInteger; public class UnsignedLongExample public static void main(String[] args) BigInteger bigInteger = BigInteger.valueOf(Long.MAX_VALUE) .add(BigInteger.valueOf(25634)); ULong uLong = ULong.valueOf(bigInteger.longValue()); System.out.println(uLong); > >
Rupam Saini is an android developer, who also works sometimes as a web developer., He likes to read books and write about various things.