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

Afs*_*ani 27 javascript numbers bitwise-operators

假设我们1在基数2中有这个数字是:

00000000000000000000000000000001
Run Code Online (Sandbox Code Playgroud)

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

11111111111111111111111111111110
Run Code Online (Sandbox Code Playgroud)

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

console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)
Run Code Online (Sandbox Code Playgroud)

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

Cer*_*rus 60

1和之间有2个整数-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');
}
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Run Code Online (Sandbox Code Playgroud)

  • @AfshinMehrabani仅按位运算符[将操作数视为32位整数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators).毫不奇怪,这就是为什么你在解析它的时候得到`-2`但是`4294967294`. (4认同)
  • 凉.还有一个问题,如果`11111111111111111111111111111110`是`-2`,那么为什么`parseInt('11111111111111111111111111111110',2)`是'4294967294`? (3认同)
  • 在数学上,在上面的每一行中,它表示"在基数2"应该真的说"在2的补码".基数2中的-2为-10,只有在计算机中才有必要存储符号.此外,还有无符号数据类型仍然是基数2,但当然不是2的补码. (3认同)
  • @AfshinMehrabani这是有符号和无符号整数之间的区别. (2认同)
  • @ToddWilcox:数学上,它不仅仅是两个补码.要从任何数字中减去一个,请从右侧扫描,将零更改为1,直到遇到一个.将其更改为零并停止.从零中减去一个将导致所有零变为1.二进制补码意味着数字的MSB应该扩展到左边的所有位.一个补码意味着它应该扩展到左边的所有位*和*右边(注意二进制中,0.11111111 ......(无休止地)任意接近1,所以.... 11111111.11111111 .....(无尽的在两个方向)任意接近0. (2认同)

D-s*_*ide 19

100 -4
101 -3
110 -2
111 -1
000  0
001  1
010  2
011  3
Run Code Online (Sandbox Code Playgroud)

一个简单的方法来记住两个补码表示法的工作方式是想象它只是一个普通的二进制,除了它的最后一位对应于相同的值否定.在我设计的三位二进制补码第一位是1,第二位是2,第三位是-4(注意减号).

所以正如你所看到的那样,不是两个补码就是-(n + 1).令人惊讶的是,将它应用于两次数字会得到相同的数字:

-(-(n + 1) + 1) = (n + 1) - 1 = n
Run Code Online (Sandbox Code Playgroud)

在谈论按位时很明显,但在其算术效果方面却没有那么多.

还有几个观察结果让人们更清楚地了解它的工作方式:

注意负值是如何提升的.规则相同,仅交换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
Run Code Online (Sandbox Code Playgroud)

通过将二进制文件列表循环到那里的数字总量的一半,您将获得从零开始的典型升序二进制数序列.

-  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  /
Run Code Online (Sandbox Code Playgroud)


gro*_*taj 14

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

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

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

您可以使用称为二进制补码的编码.在此编码中,您必须确定每个数字将包含多少位(例如8位).在最显著位被保留为符号位.如果是0,则该数字应解释为非负数,否则为负数.其他7位包含实际数字.

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

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

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

让我们试试这个:-5 + 5. -5是11111011,500000101.

  11111011
+ 00000101
----------
 000000000
Run Code Online (Sandbox Code Playgroud)

结果是9位长.最重要的位溢出,我们留下的00000000是0.它似乎工作.

另一个例子:23 + -7.23是00010111-7是11111001.

  00010111
+ 11111001
----------
 100010000
Run Code Online (Sandbox Code Playgroud)

再次,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,即使二进制值看起来是相同的.


Bat*_*eba 6

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

所以一切如预期.

  • 因为在**场合,二进制表示*无符号*整数或64位有符号类型. (4认同)

小智 5

这是二进制补码算术。这等效于“磁带计数器”算法。磁带录音机倾向于附有计数器(添加机器可能会是一个更好的类比,但是当2s补码变得流行时,它们已经过时了)。

当您从000向后缠绕2步时,您将到达998。所以998是磁带计数器的10s补码算术表示形式-2:向后缠绕2步,再次达到000。

2s补码就是这样。从1111111111111111向前进1,您将到达0000000000000000,因此1111111111111111是-1的表示形式。取而代之的是从那里绕回另一个1,然后得到1111111111111110,它就是-2的表示形式。