为什么按位“不是1”等于-2?

时间:2015-07-13 07:18:56

标签: javascript numbers bitwise-operators

假设我们有1,而基数2中的这个数字是:

00000000000000000000000000000001

现在我想翻转所有位以获得以下结果:

11111111111111111111111111111110

据我所知,解决方案是使用~(按位NOT运算符)来翻转所有位,但~1的结果是-2

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

为什么我会得到这个奇怪的结果?

7 个答案:

答案 0 :(得分:58)

1-2之间有2个整数:0-1

二进制文件中的

100000000000000000000000000000001
二进制000000000000000000000000000000000
二进制-111111111111111111111111111111111
二进制-211111111111111111111111111111110
“binary”是2的补码,在按位的情况下不是~

正如您所看到的,~1等于-2并不奇怪,因为~0等于-1

作为@Derek explained,这些bitwise operators将其操作数视为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; }

答案 1 :(得分:20)

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。 Bitwise NOTted,如果你愿意的话。

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's 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  /

答案 2 :(得分:15)

在计算机科学中,它完全与解释有关。对于计算机而言,一切都是可以以多种方式解释的位序列。例如,0100001可以是数字33或!ASCII如何映射此位序列。)

计算机的所有内容都是一个序列,无论您将其视为数字,数字,字母,文本,Word文档,屏幕上的像素,显示的图像还是硬盘驱动器上的JPG文件。如果您知道如何解释该位序列,它可能会变成对人类有意义的东西,但在RAM和CPU中只有位。

因此,当您想要在计算机中存储号码时,您必须编码它。对于非负数,它非常简单,你只需要使用二进制表示。但负面数字呢?

您可以使用名为两个补码的编码。在此编码中,您必须确定每个数字将包含多少位(例如8位)。 most significant bit保留为符号位。如果它是0,则该数字应被解释为非负数,否则为负数。其他7位包含实际数字。

00000000表示零,就像无符号数字一样。 00000001是一个,00000010是两个,依此类推。您可以在两个补码中以8位存储的最大正数是127(01111111)。

下一个二进制数(10000000)是-128。这可能看起来很奇怪,但在一瞬间我会解释为什么它有意义。 10000001为-127,10000010为-126,依此类推。 11111111为-1。

为什么我们使用这种奇怪的编码?因为它有趣的特性。具体来说,在执行加法和减法时,CPU不必知道它是一个存储为二进制补码的有符号数。它可以将这两个数字解释为无符号,将它们加在一起,结果将是正确的。

让我们试试这个:-5 + 5. -5是11111011500000101

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

结果是9位长。最重要的位溢出,我们留下00000000为0.它似乎有效。

另一个例子:23 + -7。 23是00010111, - 7是11111001

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

同样,MSB丢失,我们得到00010000 == 16.它有效!

这是两个补充的运作方式。计算机在内部使用它来存储有符号整数。

您可能已经注意到,当您取消数字N的位时,在两个补码中,它会变为-N-1。例子:

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

这正是你所观察到的:JS假装它使用了两个补码。那么为什么parseInt('11111111111111111111111111111110', 2)是4294967294?好吧,因为它只是假装。

内部JS总是使用浮点数表示。它的工作方式与两个补码完全不同,其按位否定大多无用,因此JS假装一个数字是二进制补码,然后否定其位并将其转换回浮点表示。这不会发生在parseInt,所以你得到4294967294,即使二进制值看似相同。

答案 3 :(得分:6)

2的补码32位有符号整数(Javascript坚持使用32位有符号整数的格式)将-2存储为11111111111111111111111111111110

所有一切都如期待。

答案 4 :(得分:4)

这是预期的行为。根据{{​​3}}。

您可能不理解的部分是 [11111111111111111111111111111110]₂ = [10]₂¹,如果表示为有符号整数。前导1可以是您想要的任意数量,它仍然是相同的数字,类似于无符号整数/小数中的前导0

¹[10]₂指定10应解释为基数2(二进制)

答案 5 :(得分:4)

它是两个补码算术。这相当于&#34;磁带计数器&#34;算术。录音机倾向于附加计数器(添加机器可能是一个更好的类比,但是当2s补充变得髋关节时它们已经过时了。)

当你从000向后蜿蜒2步时,你到达998.所以998是磁带计数器的10s补码算术表示-2:前进2步,再次到达000。

2s补充就是这样。从1111111111111111向前转1并且到达0000000000000000,因此1111111111111111是-1的表示。风反而从那里返回另一个1,你得到1111111111111110,然后是-2的表示。

答案 6 :(得分:3)

Numbers in JavaScript are floating point numbers,由IEEE 754标准存储和表示。

但是,对于按位运算,操作数在内部被视为signed 32-bit integers represented by two's complement format

  

所有位运算符的操作数都转换为带符号的32位   二进制补码格式的整数。二进制补码格式意味着   数字的负对应物(例如5对-5)就是全部   数字的位被反转(按位数不是数字,a.k.a。''   补数(加上一个)。

负数的正对应方以相同的方式计算。因此我们有:

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

请注意,Number.toString()不应返回base-2的二进制补码表示。

表达式(-2).toString(2)产生-10,这是减号(-),后跟210)的基数2表示。