Почему побитовое "не 1" равно -2?

Предположим, что 1 и это число в базе 2:

00000000000000000000000000000001

Теперь я хочу перевернуть все биты, чтобы получить следующий результат:

11111111111111111111111111111110

Насколько мне известно, решение состоит в том, чтобы использовать ~ (побитовый оператор NOT) для переключения всех битов, но результат ~1 равен -2:

console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)

Почему я получаю этот странный результат?

Ответ 1

Есть два целых числа между 1 и -2: 0 и -1

1   в двоичном формате 00000000000000000000000000000001
0   в двоичном формате 00000000000000000000000000000000
-1 в двоичном формате 11111111111111111111111111111111
-2 в двоичном формате 11111111111111111111111111111110
( "двоичный", являющийся 2 дополнением, в случае побитового не ~)

Как вы можете видеть, не удивительно, что ~1 равно -2, так как ~0 равно -1.

Как @Derek объяснено, Эти побитовые операторы обрабатывают их операнды как последовательность из 32 бит. parseInt, с другой стороны, нет. Вот почему вы получаете разные результаты.


Здесь более полная демонстрация:

for (var i = 5; i >= -5; i--) {
  console.log('Decimal: ' + pad(i, 3, ' ') + '  |  Binary: ' + bin(i));
  if (i === 0)
    console.log('Decimal:  -0  |  Binary: ' + bin(-0)); // There is no `-0`
}

function pad(num, length, char) {
  var out = num.toString();
  while (out.length < length)
    out = char + out;
  return out
}

function bin(bin) {
  return pad((bin >>> 0).toString(2), 32, '0');
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Ответ 2

100 -4
101 -3
110 -2
111 -1
000  0
001  1
010  2
011  3

Простой способ вспомнить, как работают две нотации дополнения, представьте, что это просто нормальный двоичный файл, за исключением того, что его последний бит соответствует тому же значению, что и отрицание. В моем умудренном трехбитном двоичном бите второй бит 1, второй - 2, третий - -4 (обратите внимание на минус).

Итак, как вы можете видеть, поразрядное не в двух дополнениях -(n + 1). Удивительно, но применяя его к числу дважды, вы получаете одинаковое число:

-(-(n + 1) + 1) = (n + 1) - 1 = n

Это очевидно, если говорить поразрядным, но не столько в его арифметическом эффекте.

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

Обратите внимание, как поднимаются отрицательные значения. Совсем те же правила, только с 0 и 1 заменены. Поименованно, если вы это сделаете.

100 -4  011 - I bitwise NOTted this half
101 -3  010
110 -2  001
111 -1  000
----------- - Note the symmetry of the last column
000  0  000
001  1  001
010  2  010
011  3  011 - This one left as-is

Цитируя этот список двоичных файлов на половину от общего количества номеров, вы получаете типичную последовательность восходящих двоичных чисел, начиная с нуля.

-  100 -4  \
-  101 -3  |
-  110 -2  |-\  - these are in effect in signed types
-  111 -1  / |
*************|
   000  0    |
   001  1    |
   010  2    |
   011  3    |
*************|
+  100  4  \ |
+  101  5  |-/  - these are in effect in unsigned types
+  110  6  |
+  111  7  /

Ответ 3

В информатике все об интерпретации. Для компьютера все это последовательность бит, которая может быть интерпретирована многими способами. Например, 0100001 может быть либо числом 33, либо ! (то, как ASCII отображает эту последовательность бит).

Все это бит-последовательность для компьютера, независимо от того, видите ли вы его как цифру, число, букву, текст, документ Word, пиксель на экране, отображаемое изображение или файл JPG на жестком диске. Если вы знаете, как интерпретировать эту последовательность бит, она может быть превращена в нечто значимое для человека, но в ОЗУ и ЦП есть только биты.

Поэтому, когда вы хотите сохранить номер на компьютере, вы должны его закодировать. Для неотрицательных чисел это довольно просто, вам просто нужно использовать двоичное представление. Но как насчет отрицательных чисел?

Вы можете использовать кодировку, называемую двумя дополнениями. В этой кодировке вы должны решить, сколько бит будет иметь каждый номер (например, 8 бит). самый значительный бит зарезервирован как знаковый бит. Если это 0, то число должно интерпретироваться как неотрицательное, иначе оно отрицательно. Другие 7 бит содержат фактическое число.

00000000 означает нуль, как и для беззнаковых чисел. 00000001 равно единице, 00000010 равно двум и так далее. Наибольшее положительное число, которое вы можете сохранить на 8 бит в двух дополнениях, - 127 (01111111).

Следующее двоичное число (10000000) равно -128. Это может показаться странным, но через секунду я объясню, почему это имеет смысл. 10000001 - -127, 10000010 - -126 и т.д. 11111111 равен -1.

Почему мы используем такую ​​странную кодировку? Из-за его интересных свойств. В частности, при выполнении сложения и вычитания ЦП не должен знать, что это подписанное число, сохраненное как два дополнения. Он может интерпретировать оба числа как unsigned, добавлять их вместе, и результат будет правильным.

Попробуем это: -5 + 5. -5 is 11111011, 5 is 00000101.

  11111011
+ 00000101
----------
 000000000

Результат - 9 бит. Большинство значительных бит переполняется, и мы остаемся с 00000000, который равен 0. Кажется, что это работает.

Другой пример: 23 + -7. 23 является 00010111, -7 is 11111001.

  00010111
+ 11111001
----------
 100010000

И снова MSB теряется, и мы получаем 00010000 == 16. Он работает!

Как работает два дополнения. Компьютеры используют его внутри, чтобы хранить целые числа со знаком.

Возможно, вы заметили, что в двух дополнениях, когда вы отрицаете биты числа N, он превращается в -N-1. Примеры:

  • 0 negated == ~00000000 == 11111111 == -1
  • 1 negated == ~00000001 == 11111110 == -2
  • 127 negated == ~01111111 == 10000000 == -128
  • 128 negated == ~10000000 == 01111111 == 127

Это именно то, что вы заметили: JS притворяется, что использует два дополнения. Итак, почему parseInt('11111111111111111111111111111110', 2) - 4294967294? Ну, потому что он только притворяется.

Внутренне JS всегда использует представление чисел с плавающей запятой. Он работает совершенно по-другому, чем два дополнения, и его побитовое отрицание в основном бесполезно, поэтому JS делает вид, что число является двумя дополнениями, а затем сбрасывает его биты и преобразует их обратно в представление с плавающей запятой. Это не происходит с parseInt, поэтому вы получаете 4294967294, хотя двоичное значение похоже одно и то же.

Ответ 4

A 32-разрядное целое число со знаком 32 бит (Javascript настаивает, что это формат, используемый для 32-разрядного целого числа со знаком) будет хранить -2 как 11111111111111111111111111111110

Итак, все как ожидалось.

Ответ 5

Это ожидаемое поведение. Согласно mdn: побитовое, а не.

Часть, которую вы, вероятно, не понимаете, [11111111111111111111111111111110]₂ = [10]₂ ¹, если выражено как целое число со знаком. Ведущим 1 может быть столько, сколько вы хотите, и оно по-прежнему совпадает с числом 0 в целых числах без знака/десятичной дроби.

¹ [10]₂ указывает, что 10 следует интерпретировать как базу 2 (двоичный)

Ответ 6

Это две арифметические дополнения. Что эквивалентно арифметике "ленточный счетчик". Магнитофоны имели тенденцию прикреплять счетчики (добавление машин, вероятно, было бы еще лучшей аналогией, но они были устаревшими уже, когда дополнение 2s стало хип).

Когда вы отступаете назад на 2 шага от 000, вы достигаете 998. Таким образом, 998 - это счетчик 10s для счетчика ленты, который выполняет арифметическое представление для -2: ветер вперед 2 шага, снова прибудет 000.

2s - это так. Надвиньте вперед 1 от 1111111111111111, и вы достигнете 0000000000000000, поэтому 1111111111111111 - это представление -1. Ветер вместо этого назад еще один оттуда, и вы получите 1111111111111110, который затем представляет собой -2.

Ответ 7

Числа в JavaScript - это номера с плавающей запятой, сохраненные и представленные стандартом IEEE 754.

Однако для побитовых операций операнды внутренне рассматриваются как подписанные 32-битные целые числа, представленные двумя дополнительными форматами:

Операнды всех побитовых операторов преобразуются в подписанные 32-разрядные целые числа в двух дополнительных форматах. Два варианта формата что число отрицательных копий (например, 5 против -5) - это все число бит инвертировано (побитовое NOT номера, a.k.a. дополнение числа) плюс один.

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

 1 = 00000000000000000000000000000001b
~1 = 11111111111111111111111111111110b
     11111111111111111111111111111110b = -2

Обратите внимание, что Number.toString() не должно возвращать два представления представления для base-2.

Выражение (-2).toString(2) дает -10, который является знаком минус (-), за которым следует представление base-2 2 (10).