Как именно побитовые операторы работают в Java?

В настоящее время я пытаюсь разобраться с побитовыми операторами и операторами сдвига битов в 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. Поскольку в длинном 0xffffffff все единицы находятся в младших 4 байтах, и поскольку только в этих байтах будут данные, эта маска не действует, и возвращается правильный результат.

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

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

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

0xffffffff — это не все единицы. Каждый ff — это один байт, и таких пар четыре. Длинный — 8 байт. Итак, это 4 байта (32 бита) нулей, а затем 4 байта единиц. Это помогает?

yshavit 10.04.2019 06:01

@yshavit Это одно недоразумение (или, скорее, недоразумение - я знал о том, что вы говорите, но по какой-то причине не применил это), но это не сходится. Я отредактировал вопрос, чтобы исправить это недоразумение.

john01dav 10.04.2019 06:03

Когда вы переводите свой отрицательный int в long, верхние 32 бита заполняются единицами. Выполнение операции & меняет их обратно на нули.

Dawood ibn Kareem 10.04.2019 06:13

Может это поможет System.out.printf("%s & %s = %s%n", Long.toBinaryString((long) x), Long.toBinaryString(0xffffffffL), Long.toBinaryString(((long)x) & 0xffffffffL));

Elliott Frisch 10.04.2019 06:15

@DawoodibnKareem Если верхние 32 бита заполнятся единицами, то величина long станет действительно огромной, возможно, близкой к максимальному значению. В Java преобразование типа int в тип long должно поддерживать одно и то же числовое значение. Таким образом, мое понимание того, что такое приведение, противоречит вашему утверждению о том, что верхние 32 бита заполняются единицами. Вы можете уточнить?

john01dav 10.04.2019 06:25

Самый большой бит отрицательный. В сумме это на единицу больше (по величине), чем все остальные биты вместе взятые. Таким образом, те, что в основном, компенсируют друг друга.

Dawood ibn Kareem 10.04.2019 06:47

@ john01dav Ваше описание всей ситуации, включая то, что вы уже знаете и что, по вашему мнению, произойдет, все это превосходно. Я бы хотел, чтобы каждый вопрос здесь, на Stack Overflow, был написан в этом стиле.

Roland Illig 10.04.2019 07:28

не уверен, что это заслуживает тега языкового юриста, но, тем не менее, хороший вопрос

CoffeeTableEspresso 15.04.2019 18:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
8
8
429
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Иногда проще всего выполнить код, используя точки останова. Для вашего конкретного примера я преобразовал шаги операции в атомарные операторы и напечатал результаты с помощью 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 все (строго числовые) значения подписаны (символы беззнаковые). Это означает, что, как вы правильно сказали, все старшие биты являются битами знака. Однако интересно то, что делают остальные биты, если число отрицательное. В следующей ветке уже были рассмотрены основы «дополнения двойки»: Что такое «дополнение 2»? Как и эта страница википедии: 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 для отрицательных чисел. Таким образом, они должны быть удалены.

Небольшая поправка: chars также не подписаны. А в остальном хорошая запись!

yshavit 10.04.2019 16:20

Правильный. Спасибо, что упомянули об этом. Я уточнил свой смысл более явно. :)

TreffnonX 11.04.2019 06:45

Другие вопросы по теме