Как именно работают побитовые операторы в Java? - PullRequest
8 голосов
/ 10 апреля 2019

В настоящее время я пытаюсь обернуть голову вокруг битовых и битовых операторов в Java. Хотя они имеют смысл для меня в упрощенных примерах игрушек (в основном, положительные целые числа), мое понимание рушится, как только появляются негативы, и в некоторых других случаях. Я пытался искать по всему Интернету с помощью двух поисковых систем, и я даже проверил спецификацию Java. Я не могу найти источник, который правильно описывает, как работают побитовые и битовые операторы в Java.

Одна функция в стандартной библиотеке Java, которая меня особенно смущает, - java.lang.Integer.toUnsignedLong(int). Исходный код OpenJdk показан здесь (LGPLv2 с исключением classpath) с выдержкой из Javadoc:

/**
 * Converts the argument to a {@code long} by an unsigned
 * conversion.  In an unsigned conversion to a {@code long}, the
 * high-order 32 bits of the {@code long} are zero and the
 * low-order 32 bits are equal to the bits of the integer
 * argument.   
 */
public static long toUnsignedLong(int x) {
    return ((long) x) & 0xffffffffL;
}

Согласно официальной документации, воспроизведенной выше, «старшие 32 бита длинной равны нулю, а младшие 32 бита равны битам целочисленного аргумента». Однако я не вижу, как это следует из кода внутри тела метода.

Когда я читаю этот метод, мой ход мыслей о положительном x следующий:

  1. Когда целое число приводится к длинному, его знаковый бит / старший значащий бит равен нулю. Таким образом, бит знака старшего разряда / старший значащий бит равен нулю, а младшие биты равны битам целого числа.
  2. Поскольку long 0xffffffff содержит все единицы в младших 4 байтах, и поскольку только эти байты будут содержать данные, эта маска не действует и возвращается правильный результат.

Однако, когда я читаю это в контексте негатива x, мое понимание расходится:

  1. Когда целое число от cst до long, его знаковый бит / старший значащий бит равен единице. Таким образом, бит знака младшего разряда / старший значащий он равен единице, а биты младшего разряда равны битам целого числа, за исключением того, что старший бит четвертого младшего байта равен нулю, когда он равен единице в целом числе.
  2. Поскольку long 0xffffffff содержит все единицы в 4 байтах самого низкого порядка и ноль в четырех байтах самого высокого порядка, он имеет единственный эффект изменения бит знака в длинной и сохраняет неверное целое число в четыре младших значащих бита нетронуты. Таким образом, он возвращает неправильный ответ от этого метода, где знаковый бит целого числа изменяется при переходе к длинному.

Однако, когда я тестирую этот метод, я получаю результаты, соответствующие Javadoc. Я подозреваю, что я неправильно понимаю один или несколько фундаментальных моментов о побитовых операторах в Java или целочисленное представление его двух дополнений, и я надеюсь, что этот вопрос может прояснить эти моменты.

1 Ответ

5 голосов
/ 10 апреля 2019

Битовые операторы работают точно так, как вы ожидаете.Они являются строгими бит-операторами и вообще не учитывают семантику битов.

Иногда проще всего выполнить код, используя точки останова.Для вашего конкретного примера я преобразовал шаги операции в атомарные операторы и напечатал результаты с помощью Long.toString.

int x = -57;

// step 1:
long xCast = (long) x;
System.out.println(Long.toString(xCast, 2)); // -1110011 - this is not the bitwise representation however.

long mask = 0xffffffffL;
System.out.println(Long.toString(mask, 2)); // 11111111111111111111111111111111

// step 2:
long result = ((long) x) & mask;
System.out.println(Long.toString(result, 2)); // 11111111111111111111111111000111

Шаг 1 - основная причина, по которой операция выглядит так, как она выглядит.В Java все (строго числовые) значения подписаны (символы без знака).Это означает, что, как вы правильно сказали, все старшие биты являются знаковыми битами.Однако интересная часть - это то, что делают остальные биты, если число отрицательно.Следующий поток уже охватывал основы «дополнения к двум»: Что такое «дополнение к двум»? Так же, как эта страница википедии: https://en.wikipedia.org/wiki/Two%27s_complement

Чтобы сделать его коротким, в Java, для целых чисел:

int zero = 0; // == 0b00000000_00000000_00000000_00000000

int maxPositive = Integer.MAX_VALUE; // == 0b01111111_11111111_11111111_11111111

int minus1 = -1; // == 0b11111111_11111111_11111111_11111111

int minNegative = Integer.MIN_VALUE; // == 0b10000000_00000000_00000000_00000000

Таким образом, причина, по которой все работает, в том, что если целое число отрицательно, когда оно приведено, все старшие 32 бита преобразуются в 1 с, потому что в противном случае представленное значение числаизменилось бы.фактически:

int x = 0b11111111_11111111_11111111_11000111;

приводится к:

long xCast = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11000111;

Поскольку вы, как разработчик, ожидаете, что метод вернет только первоначально установленные биты, вы должны замаскировать верхние биты изрезультат.Это делается на шаге 2.

Итак, ответ на ваш пример: представление неплавающих значений в Java является дополнением к двум, и поэтому при умном приведении значения от int к long верхние битызаполнены 1 для отрицательных чисел.Таким образом, они должны быть удалены.

...