Почему Java маскирует операнды с 0x1F?
В Java:
(0xFFFFFFFF << 1) = 0xFFFFFFFE = 0b1111111111111110
: : :
(0xFFFFFFFF << 30) = 0xE0000000 = 0b1110000000000000
(0xFFFFFFFF << 30) = 0xC0000000 = 0b1100000000000000
(0xFFFFFFFF << 31) = 0x80000000 = 0b1000000000000000
Однако:
(0xFFFFFFFF << 32) = 0xFFFFFFFF = 0b1111111111111111
Логически это не имеет никакого смысла, но, по моему мнению, это Java, выполняющая операцию, похожую на:
a << (b % Integer.SIZE)
[edit, по-видимому:] a << (b & 0x1F)
Это относится и к >>
и >>>
.
Очевидно, что сдвиг по >= 32 (в случае Integer) удаляет все данные из типа данных, но есть моменты, когда это полезно. Например:
int value = 0x3F43F466; // any value
int shift = 17; // any value >= 0
int carry = value & (-1 << (Integer.SIZE - shift));
if (carry > 0)
; // code...
Конечно, это может быть исправлено, но найти эти ошибки можно довольно много (я просто потратил часы, отслеживая аналогичный вариант).
Итак, мой вопрос: Есть ли причина для отказа от логического значения при смещении всех битов?
UPDATE:
Я попробовал это на C99, используя следующее:
#include<stdio.h>
main()
{
int i, val;
for (i = 0; i <=36; i++) {
val = (-1 << i);
printf("%d :\t%d\n", i, val);
}
}
Я обнаружил, что он ведет себя так же, как Java, маскируя i & 0x1F
, тогда как он дает предупреждение при компиляции при заданном постоянном значении:
warning: left shift count >= width of type
Ответы
Ответ 1
Конечно, есть: как большинство процессоров (в частности, x86) реализуют смещение битов и делают то, что вы хотите - проверить, больше ли сдвиг, чем 32, и если да, то вернуть нуль - требуется ветвь, что может быть дорогостоящим для современных процессоров. Это не просто "раздражает", это может замедлить работу на порядок.
Короче говоря, выполнение того, что вы хотите, приведет к значительным накладным расходам для операции, которая, как ожидается, быстро разгорается высокопроизводительным кодом.
Для справки логика не совсем такая же, как %
, это маска. Подробнее см. JLS 15.19:
Если продвинутый тип левого операнда является int, в качестве расстояния сдвига используются только пять младших бит правого операнда. Это как если бы правый операнд подвергался поразрядному логическому оператору И и (§15.22.1) с значением маски 0x1f (0b11111). Поэтому фактически используемое расстояние сдвига всегда находится в диапазоне от 0 до 31 включительно.
Ответ 2
JLS 15.19 Если продвинутый тип левого операнда является int, в качестве расстояния сдвига используются только пять младших бит правого операнда. Это как если бы правый операнд подвергался поразрядному логическому оператору И и (§15.22.1) с значением маски 0x1f (0b11111). Поэтому фактически используемое расстояние сдвига всегда находится в диапазоне от 0 до 31 включительно
просто 0xFFFFFFFF << 32
эквивалентен to 0xFFFFFFFF << (32 & 0x1f)
эквивалентен 0xFFFFFFFF << 0