Java: сдвиг вправо на отрицательном числе

Я очень запутался в операции сдвига вправо на отрицательном числе, вот код.

int n = -15;
System.out.println(Integer.toBinaryString(n));
int mask = n >> 31;
System.out.println(Integer.toBinaryString(mask));

И результат:

11111111111111111111111111110001
11111111111111111111111111111111

Почему сдвиг вправо отрицательного числа на 31, а не на 1 (знаковый бит)?


person Cacheing    schedule 17.03.2013    source источник
comment
Кстати, вы можете использовать >>> -1, и он будет работать для типов int и long.   -  person Peter Lawrey    schedule 17.03.2013


Ответы (3)


Поскольку в Java нет беззнаковых типов данных, существует два типа сдвигов вправо: арифметический сдвиг >> и логический сдвиг >>>. http://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html

Арифметический сдвиг >> сохранит бит знака.
Арифметический сдвиг

Беззнаковый сдвиг >>> не сохранит бит знака (таким образом, заполнив 0s).
Логический сдвиг

(изображения из Википедии)


Кстати, как арифметический сдвиг влево, так и логический сдвиг влево дают один и тот же результат, поэтому сдвиг влево только один <<.

person Alvin Wong    schedule 17.03.2013
comment
Что произойдет, если вы сделаете 10101010 << 1? 11010100 или 01010100? - person Aurélien Ooms; 17.04.2013
comment
Я имею в виду, действительно ли By the way, both arithmetic shift and logical shift have the same result, so there is only one left shift << правда? - person Aurélien Ooms; 17.04.2013

Оператор >>, вызываемый Сдвиг вправо со знаком, сдвигает все биты вправо указанное количество раз. Важно то, что >> заполняет самый левый бит знака (самый значащий бит MSB) до самого левого бита после сдвига. Это называется расширением знака и служит для сохранения знака отрицательных чисел, когда вы сдвигаете их вправо.

Ниже приведено мое схематическое представление с примером, показывающим, как это работает (для одного байта):

Пример:

i = -5 >> 3;  shift bits right three time 

Пять в форме дополнения до двух - это 1111 1011

Представление памяти:

 MSB
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
   7    6   5    4   3   2   1   0  
  ^  This seventh, the left most bit is SIGN bit  

И ниже, как работает >>? Когда вы делаете -5 >> 3

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 1 | 1 | 1 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated

Обратите внимание: самые левые три бита равны единице, потому что каждый бит знака сдвига сохраняется, и каждый бит тоже правый. Я написал Знак распространяется, потому что все эти три бита связаны со знаком (но не с данными).

Также из-за трех сдвигов вправо большинство трех битов теряются.

Биты между двумя правыми стрелками отображаются из предыдущих битов в -5.

Я думаю, было бы хорошо, если бы я написал пример и для положительного числа. Следующий пример — 5 >> 3, а пять — это один байт 0000 0101.

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 1 | 0 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 0 | 0 | 0 | 0 | 0 |
+----+----+----+---+---+---+---+---+
(______________)
 The sign is        
 propagated

Посмотрите еще раз, я пишу Знак распространяется, так что крайние три нуля связаны с битом знака.

Вот что делает оператор >> Сдвиг вправо со знаком, сохраняя знак левого операнда.

[ваш ответ]
В вашем коде вы сдвигаете -15 вправо 31 раз, используя оператор >>, так что ваши самые правые 31 биты теряются, и в результате все биты 1 фактически имеют величину -1.

Вы заметили, что таким образом -1 >> n эквивалентно не утверждению.
Я считаю, что если кто-то делает i = -1 >> n, компиляторы Java должны оптимизировать его до i = -1, но это другое дело

Далее, было бы интересно узнать, что в Java доступен еще один оператор сдвига вправо >>>, который называется Сдвиг вправо без знака. И это работает логично и заполняет нуль слева для каждой операции сдвига. Таким образом, при каждом сдвиге вправо вы всегда получаете нулевой бит в крайнем левом положении, если вы используете оператор беззнакового сдвига вправо >>> как для отрицательных, так и для положительных чисел.

Пример:

i = -5 >>> 3;  Unsigned shift bits right three time 

А ниже моя диаграмма, демонстрирующая, как работает выражение -5 >>> 3?

                        this 3 bits are shifted 
                         out and loss
 MSB                   (___________)      
+----+----+----+---+---+---+---+---+
|  1 |  1 | 1  | 1 | 1 | 0 | 1 | 1 |   
+----+----+----+---+---+---+---+---+
  | \                 \  
  |  ------------|     ----------|
  |              |               |
  ▼              ▼               ▼
+----+----+----+---+---+---+---+---+
|  0 |  0 | 0  | 1 | 1 | 1 | 1 | 1 |
+----+----+----+---+---+---+---+---+
(______________)
  These zeros
  are inserted  

И вы можете заметить: на этот раз я не пишу, что знаковые биты распространяются, а на самом деле оператор >>> вставляет нули. Следовательно, >>> не сохраняет знак, а выполняет логический сдвиг вправо.

Насколько я знаю, сдвиг вправо без знака полезен в VDU (графическое программирование), хотя я не использовал его, но читал где-то в прошлом.

Я бы посоветовал вам прочитать это: Разница между >>> и >> : >> — арифметический сдвиг вправо, >>> — логический сдвиг вправо.

Изменить:

Немного интересного об операторе беззнакового сдвига вправо >>>.

  • Оператор беззнакового сдвига вправо >>> создает чистое значение, которое представляет собой его левый операнд, сдвинутый вправо с нулевым расширением 0 на число битов, указанное его правым операндом.

  • Подобно >> и <<, оператор >>> тоже оператор никогда не выдает исключения.

  • Тип каждого операнда беззнакового оператора сдвига вправо должен быть целочисленным типом данных, иначе произойдет ошибка времени компиляции.

  • Оператор >>> может выполнять преобразования типов своих операндов; в отличие от арифметических бинарных операторов, каждый операнд преобразуется независимо. Если тип операнда — byte, short или char, этот операнд преобразуется в int перед вычислением значения оператора.

  • Тип значения, создаваемого беззнаковым оператором сдвига вправо, является типом его левого операнда. LEFT_OPERAND >>> RHIGT_OPERAND

  • Если преобразованный тип левого операнда — int, в качестве расстояния сдвига используются только пять младших битов значения правого операнда. (то есть 25 = 32 бита = количество битов в int)
    Таким образом, расстояние сдвига находится в диапазоне от 0 до 31.

    Здесь значение, создаваемое r >>> s, такое же, как:

    s==0 ? r : (r >> s) & ~(-1<<(32-s))
    
  • Если тип левого операнда длинный, то в качестве расстояния сдвига используются только шесть младших битов значения правого операнда.(то есть 25 = 64 бита = количество бит в long)

    Здесь значение, созданное r >>> s, такое же, как следующее:

    s==0 ? r : (r >> s) & ~(-1<<(64-s))
    

Интересная справка: [Глава 4] 4.7 Операторы сдвига

person Grijesh Chauhan    schedule 17.03.2013
comment
На самом деле он не «ставит 1», он просто сохраняет бит знака, каким бы он ни был. - person user207421; 17.03.2013
comment
Более простой ответ и более правильный, и более полезный. Оператор не смотрит, является ли операнд отрицательным или положительным, а затем делает две разные вещи. - person user207421; 17.03.2013
comment
Пожалуйста, придерживайтесь сути и не вмешивайтесь в личности. Ваше объяснение вдвое сложнее, чем необходимо, и, судя по вашему комментарию к ответу @AlvinWong, вы до сих пор не понимаете, почему. - person user207421; 17.03.2013
comment
Вы всегда можете попробовать упростить свой ответ, как было предложено. Я не знаю, почему это такая загадка. Я дал вам некоторую формулировку: «Он просто сохраняет знаковый бит, каким бы он ни был», и есть и другие правильные ответы, на которые стоит обратить внимание. Тогда вы можете даже получить несколько голосов up. Эффект, который вы описываете, происходит хорошо, но не так, как вы описали. Кажется, у вас проблемы с пониманием этого момента. - person user207421; 17.03.2013
comment
@GrjeshChauhan - я согласен с EJP. Этот ответ противоречит сам себе и очень сбивает с толку. - person Stephen C; 17.03.2013
comment
@GrjeshChauhan Я хотел, чтобы вы улучшили этот ответ. Это было причиной моего первоначального комментария и всего разговора. - person user207421; 18.03.2013
comment
Не очень нравится расширение знака термина, которое для меня происходит, когда вы копируете значение со знаком в слово большего размера. Копирование знаков или сохранение знаков больше похоже на это. - person Robert Lewis; 21.08.2018
comment
Спасибо за подробное описание. Вы сделали мой день. - person Ajay; 21.12.2019

Потому что >> определяется как арифметический сдвиг вправо, который сохраняет знак. Чтобы получить ожидаемый эффект, используйте логический сдвиг вправо, оператор >>>.

person user207421    schedule 17.03.2013
comment
Хорошо, что я пытался сказать, вы написали одним словом preserves the sign, это хорошо. - person Grijesh Chauhan; 17.03.2013