Javascript Shift вправо с оператором заполнения нулями (>>>) дает неожиданный результат - PullRequest
7 голосов
/ 06 августа 2020

Во-первых, (-1 >>> 0) === (2**32 - 1), который, как я ожидаю, связан с добавлением нового нуля слева, таким образом преобразовывая число в 33-битное число?

Но, почему также (-1 >>> 32) === (2**32 - 1), а я ожидать, что он (после сдвига 32-битного числа 32 раза и замены наиболее значимых бит на нули) будет 0.

Разве оно не должно быть равно ((-1 >>> 31) >>> 1) === 0? или я что-то упустил?

Ответы [ 2 ]

4 голосов
/ 06 августа 2020

Когда вы выполняете (-1 >>> 0), вы выполняете беззнаковый сдвиг вправо. Беззнаковый здесь ключ. Согласно spe c результат >>> всегда беззнаковый. -1 представлен как два дополнения из 1. В двоичном формате это все 1 s (в 8-битной системе это будет 11111111).

Итак, теперь вы делаете его беззнаковым, выполняя >>> 0. Вы говорите: «сдвиньте двоичное представление -1, которое состоит из 1 s, на нулевые биты (не делайте изменений), но заставьте его возвращать беззнаковое число». Итак, вы получаете значение всех 1 s. Go на любую javascript консоль в браузере и набираете:

console.log(2**32 - 1) //4294967295
// 0b means binary representation, and it can have a negative sign
console.log(0b11111111111111111111111111111111) //4294967295
console.log(-0b1 >>> 0) //4294967295

Помните, что 2 ** любое число минус 1 всегда все единиц в двоичном формате. Это то же количество единиц, что и степень, в которую вы подняли два. Итак, 2**32 - 1 равно 32 1 с. Например, два в третьей степени (восемь) минус один (семь) равно 111 в двоичный.

Итак, для следующего (-1 >>> 32) === (2**32 - 1) .... давайте рассмотрим несколько вещей. Мы знаем, что двоичное представление -1 - это все 1 s. Затем сдвиньте его вправо на один di git, и вы получите то же значение, что и все 1, но перед ним стоит ноль (и верните беззнаковое число).

console.log(-1 >>> 1) //2147483647
console.log(0b01111111111111111111111111111111) //2147483647

И продолжайте сдвигать, пока не получите 31 ноль и один 1 в конце.

console.log(-1 >>> 31) //1

Это имеет смысл для меня, у нас 31 0 s и один 1 теперь для наших 32 бит.

Итак, вы нажимаете странный случай, сдвиг еще раз должен привести к нулю правильно?

Согласно spe c:

6.1.6.1.11 Number::unsignedRightShift ( x, y )
Let lnum be ! ToInt32(x).
Let rnum be ! ToUint32(y).
Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
Return the result of performing a zero-filling right shift of lnum by shiftCount bits. Vacated bits are filled with zero. The result is an unsigned 32-bit integer.

Итак, мы знаем, что у нас уже есть -1, а это всего 1 комплимент. И мы собираемся сдвинуть его в соответствии с последним шагом документации на shiftCount бит (который, по нашему мнению, равен 32). И shiftCount это:

Пусть shiftCount будет результатом маскирования всех, кроме младших 5 битов rnum, то есть вычислить rnum & 0x1F.

Так что же такое rnum & 0x1F? Ну & означает поразрядную операцию AND. lnum - это число слева от >>>, а rnum - это число справа от него. Итак, мы говорим 32 AND 0x1F. Помните, что 32 - это 100000. 0x является шестнадцатеричным, где каждый символ может быть представлен 4 битами. 1 равно 0001, а F - 1111. Таким образом, 0x1F равно 00011111 или 11111 (31 в базе 10, 2**5 - 1 также).

console.log(0x1F) //31 (which is 11111)

  32: 100000 &
0x1F: 011111
     ---------
      000000

Количество бит, на которое нужно сдвинуть, если он равен нулю. Это связано с тем, что начальный 1 в 32 не является частью 5 старших битов! Слишком много цифр. Итак, берем 32 1 с и сдвигаем в ноль! Поэтому. Ответ все равно 32 1 с.

В примере -1 >>> 31 это имело смысл, потому что 31 - это <= 5 бит. Итак, мы сделали

  31: 11111 &
0x1F: 11111
      -------
      11111

и сдвинули его 31 бит .... как и ожидалось.

Давайте проверим это дальше .... давайте сделаем

console.log(-1 >>> 33) //2147483647
console.log(-1 >>> 1)  //2147483647

Это имеет смысл, просто сдвиньте его на один бит.

  33: 100001 &
0x1F: 011111
      ---------
      00001

Итак, go над 5 битами с помощью побитового оператора и запутаетесь. Хотите поиграть в пустышку с человеком, который не исследовал ECMAScript, чтобы ответить на сообщение stackoverflow? Просто спросите, почему они такие же.

console.log(-1 >>> 24033) //2147483647
console.log(-1 >>> 1)     //2147483647

Ну конечно, потому что

console.log(0b101110111100001) // 24033 
console.log(0b000000000000001) // 1
//                      ^^^^^ I only care about these bits!!!    
3 голосов
/ 06 августа 2020

Когда вы выполняете (-1 >>> 0), вы превращаете знаковый бит в ноль, сохраняя при этом остальную часть числа неизменной, поэтому в итоге получается 2**32 - 1.

Следующее поведение задокументировано в Спецификация ECMAScript . Фактическое количество сдвигов будет «результатом маскирования всех 5 бит rnum, кроме 5 наименее значимых, то есть вычислить rnum & 0x1F».

Поскольку 32 & 0x1F === 0, оба ваших результата будут быть идентичным.

...