В настоящее время я пытаюсь разобраться с побитовыми операторами и операторами сдвига битов в 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 следующим образом:
0xffffffff
все единицы находятся в младших 4 байтах, и поскольку только в этих байтах будут данные, эта маска не действует, и возвращается правильный результат.Однако, когда я читаю это в контексте негатива x
, мое понимание разваливается:
0xffffffff
имеет все единицы в младших 4 байтах и ноль в старших четырех байтах, он имеет единственный эффект изменения бита знака в длинном и сохраняет неправильное целое число в четырех младших значащих битах без изменений. . Таким образом, он возвращает неверный ответ из этого метода, где бит знака целого числа изменяется по мере того, как оно переходит в длинное.Однако, когда я тестирую этот метод, я получаю результаты, соответствующие Javadoc. Я подозреваю, что неправильно понимаю один или несколько фундаментальных моментов о побитовых операторах в Java или его двух целочисленных представлениях, и я надеюсь, что этот вопрос может прояснить эти моменты.
@yshavit Это одно недоразумение (или, скорее, недоразумение - я знал о том, что вы говорите, но по какой-то причине не применил это), но это не сходится. Я отредактировал вопрос, чтобы исправить это недоразумение.
Когда вы переводите свой отрицательный int
в long
, верхние 32 бита заполняются единицами. Выполнение операции &
меняет их обратно на нули.
Может это поможет System.out.printf("%s & %s = %s%n", Long.toBinaryString((long) x), Long.toBinaryString(0xffffffffL), Long.toBinaryString(((long)x) & 0xffffffffL));
@DawoodibnKareem Если верхние 32 бита заполнятся единицами, то величина long станет действительно огромной, возможно, близкой к максимальному значению. В Java преобразование типа int в тип long должно поддерживать одно и то же числовое значение. Таким образом, мое понимание того, что такое приведение, противоречит вашему утверждению о том, что верхние 32 бита заполняются единицами. Вы можете уточнить?
Самый большой бит отрицательный. В сумме это на единицу больше (по величине), чем все остальные биты вместе взятые. Таким образом, те, что в основном, компенсируют друг друга.
@ john01dav Ваше описание всей ситуации, включая то, что вы уже знаете и что, по вашему мнению, произойдет, все это превосходно. Я бы хотел, чтобы каждый вопрос здесь, на Stack Overflow, был написан в этом стиле.
не уверен, что это заслуживает тега языкового юриста, но, тем не менее, хороший вопрос
Побитовые операторы работают именно так, как вы ожидаете. Они являются строгими битовыми операторами и вообще не учитывают семантику битов.
Иногда проще всего выполнить код, используя точки останова. Для вашего конкретного примера я преобразовал шаги операции в атомарные операторы и напечатал результаты с помощью 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
также не подписаны. А в остальном хорошая запись!
Правильный. Спасибо, что упомянули об этом. Я уточнил свой смысл более явно. :)
0xffffffff — это не все единицы. Каждый
ff
— это один байт, и таких пар четыре. Длинный — 8 байт. Итак, это 4 байта (32 бита) нулей, а затем 4 байта единиц. Это помогает?