i & 0xFF vs i % 256 带负数

i & 0xFF vs i % 256 with negative numbers

我想知道为什么 (i % 256) 和 (i & 0xFF) 如果 i 为 0 或正数则相同,但如果 i 为负数则完全不同。

用于测试的代码:

i = -100
while (i < 100) {
    if (!((i & 0xFF) == (i % 256))) console.log(i);
    i++;
}

这是为什么?

先总结一下运算符:

& 是按位与运算符。

  • 例如,对于正 4 位数,
0110 (6)
0100 (4) &
-------
0100 (4)
  • 例如,对于负 4 位(2 的补码)数,
1010 (-6)
1100 (-4) &
-------
1000 (-8)

% 是模运算符。 x % y 大致意思是 x - parseInt(x / y) * x

  • 例如,对于正数,8 % 3 === 2
  • 例如,对于负数,-8 % 3 === -2

现在回答问题

为了我们的解释,让我们简单地使用 i & 0b0011 (3)1 % 0b0100 (4) 而不是 255 和 256。这对于这个问题的目的是等价的,因为 256 在二进制中是 10000...0xff 在二进制中是 01111....

  • 对于小于 4 的正数:i & 0b00111 % 0b0100 都将 return i.

2 % 4 = 4

0010 (2)
0011 (3) &
-------
0010 (2)
  • 对于大于或等于 4 的正数:i & 0b00111 % 0b0100

7 % 4 = 3

0111 (7)
0011 (3) &
-------
0011 (3)

出现这种情况是因为这些操作不同,所以你不会问为什么2+2等于2*2,而是2+3 != 2*3,对吧?

无论如何,主要问题是数字的按位表示不是1:1关系,不同的数字可以有相同的表示(检查Signed versus Unsigned Integers。了解一些细节)

所以,% 模运算符取除法的余数并且它有符号,所以

console.log(-100 % 256); // shows -100 which is true

按位 & 与物理位一起工作,它不理解符号位集,因此,如果我们查看 -100 的按位表示 - 它是 10011100 同时它是156

的按位表示

console.log((0b10011100)); // unsigned 156
console.log((0b10011100 << 24 >> 24)); // signed -100

负数的表示形式为two's complement,即-100的二进制表示为

(2**32 - 100).toString(2) = 11111111111111111111111110011100

加上0xff得到10011100,也就是156.

取模运算%定义为

IF a % b == r  THEN a == b * q + r for some q

总是有qr两种选择,有正余数和负余数。例如,使用 7 % 3

 7 = 3 * 2 + 1
 7 = 3 * 3 - 2

负数也一样,-7 % 3:

 -7 = 3 * (-2) - 1
 -7 = 3 * (-3) + 2

对于正数,所有语言都选择正余数。对于负数,选择因语言而异。例如在 python 中,余数总是正数,所以

 -7 % 3 = 2   # python

Javascript 取负余数,所以在 JS

 -7 % 3 = -1  // javascript

同样,对于-100 % 256

-100 = 256 * ( 0) - 100
-100 = 256 * (-1) + 156

在python中,余数是156(因此匹配& FF),在javascript中是-100