Javascript 使用零填充运算符 (>>>) 的右移产生意外结果
Javascript's Shift right with zero-fill operator (>>>) yielding unexpected result
首先,(-1 >>> 0) === (2**32 - 1)
我想是因为在左边加了一个新的零,从而把数字转换成33位数字?
但是,为什么 (-1 >>> 32) === (2**32 - 1)
也是 0,而我预计它(将 32 位数字移位 32 次并将最高有效位替换为零后)为 0。
不应该等于((-1 >>> 31) >>> 1) === 0
吗?还是我遗漏了什么?
当您执行 (-1 >>> 0)
时,您将符号位变为零,同时保持数字的其余部分相同,因此最终为 2**32 - 1
。
下一个行为记录在 ECMAScript specification 中。实际的移位数将是“屏蔽除 rnum 的最低有效 5 位以外的所有结果,即计算 rnum & 0x1F
”。
从 32 & 0x1F === 0
开始,您的两个结果将是相同的。
当您执行 (-1 >>> 0)
时,您正在执行无符号右移。这里的未签名是关键。根据 1
的 spec, the result of >>>
is always unsigned. -1
is represented as the two's compliment。这在二进制中都是 1
s(在 8 位系统中它是 11111111
)。
所以现在你要通过执行 >>> 0
使它无符号。您是说,“将 -1
的二进制表示形式全部 1
移动零位(不做任何更改),但使其成为 return 无符号数。”因此,您获得所有 1
的值。转到浏览器中的任何 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
s。例如,二的三次方(八)减去一(七)在二进制中是111
。
所以对于下一个 (-1 >>> 32) === (2**32 - 1)
...让我们看一些事情。我们知道-1
的二进制表示都是1
。然后将其右移一位,您将获得与所有 1
相同的值,但在其前面加上一个零(并且 return 是一个无符号数)。
console.log(-1 >>> 1) //2147483647
console.log(0b01111111111111111111111111111111) //2147483647
并继续移动,直到最后有 31 个零和一个 1
。
console.log(-1 >>> 31) //1
这对我来说很有意义,我们现在有 31 个 0
和一个 1
用于我们的 32 位。
那么你遇到了奇怪的情况,再移动一次应该使零对吗?
根据 spec:
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
是:
Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute 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
如果为零则要移动的位数。 这是因为 32
中的前导 1
不是 5
最高有效位的一部分! 32
是六位。 所以我们取 32 1
并将其移零位!这就是为什么。答案还是321
s.
在示例 -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
因此,使用按位运算符检查 5
位并感到困惑。想和一个没有研究过 ECMAScript 的人一起玩 stump the dummy 来回答 Whosebug post?就问为什么这些都一样
console.log(-1 >>> 24033) //2147483647
console.log(-1 >>> 1) //2147483647
当然是因为
console.log(0b101110111100001) // 24033
console.log(0b000000000000001) // 1
// ^^^^^ I only care about these bits!!!
首先,(-1 >>> 0) === (2**32 - 1)
我想是因为在左边加了一个新的零,从而把数字转换成33位数字?
但是,为什么 (-1 >>> 32) === (2**32 - 1)
也是 0,而我预计它(将 32 位数字移位 32 次并将最高有效位替换为零后)为 0。
不应该等于((-1 >>> 31) >>> 1) === 0
吗?还是我遗漏了什么?
当您执行 (-1 >>> 0)
时,您将符号位变为零,同时保持数字的其余部分相同,因此最终为 2**32 - 1
。
下一个行为记录在 ECMAScript specification 中。实际的移位数将是“屏蔽除 rnum 的最低有效 5 位以外的所有结果,即计算 rnum & 0x1F
”。
从 32 & 0x1F === 0
开始,您的两个结果将是相同的。
当您执行 (-1 >>> 0)
时,您正在执行无符号右移。这里的未签名是关键。根据 1
的 spec, the result of >>>
is always unsigned. -1
is represented as the two's compliment。这在二进制中都是 1
s(在 8 位系统中它是 11111111
)。
所以现在你要通过执行 >>> 0
使它无符号。您是说,“将 -1
的二进制表示形式全部 1
移动零位(不做任何更改),但使其成为 return 无符号数。”因此,您获得所有 1
的值。转到浏览器中的任何 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
s。例如,二的三次方(八)减去一(七)在二进制中是111
。
所以对于下一个 (-1 >>> 32) === (2**32 - 1)
...让我们看一些事情。我们知道-1
的二进制表示都是1
。然后将其右移一位,您将获得与所有 1
相同的值,但在其前面加上一个零(并且 return 是一个无符号数)。
console.log(-1 >>> 1) //2147483647
console.log(0b01111111111111111111111111111111) //2147483647
并继续移动,直到最后有 31 个零和一个 1
。
console.log(-1 >>> 31) //1
这对我来说很有意义,我们现在有 31 个 0
和一个 1
用于我们的 32 位。
那么你遇到了奇怪的情况,再移动一次应该使零对吗?
根据 spec:
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
是:
Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute 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
如果为零则要移动的位数。 这是因为 32
中的前导 1
不是 5
最高有效位的一部分! 32
是六位。 所以我们取 32 1
并将其移零位!这就是为什么。答案还是321
s.
在示例 -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
因此,使用按位运算符检查 5
位并感到困惑。想和一个没有研究过 ECMAScript 的人一起玩 stump the dummy 来回答 Whosebug post?就问为什么这些都一样
console.log(-1 >>> 24033) //2147483647
console.log(-1 >>> 1) //2147483647
当然是因为
console.log(0b101110111100001) // 24033
console.log(0b000000000000001) // 1
// ^^^^^ I only care about these bits!!!