我不是按位运算符的专家,但我经常看到一个模式,在比赛中由256k演示的程序员使用。而不是使用Math.floor()
函数,使用双位NOT运算符~~
(可能更快?)。
像这样:
Math.floor(2.1); // 2
~~2.1 // 2
搜索显示有更多模式使用相同的方式:
2.1 | 0 // 2
2.1 >> 0 // 2
在开发控制台中玩这个时,我注意到一种我不确定完全理解的行为。
Math.floor(2e+21); // 2e+21
~~2e+21; // -1119879168
2e+21 | 0; // -1119879168
引擎盖下发生了什么?
答案 0 :(得分:4)
正如Felix King指出的那样,这些数字正被转换为32位有符号整数。 2e9小于signed int的最大正值,因此可以:
~~(2e9) //2000000000
但是当你转到2e10时,它不能使用所有的位,所以它只需要最低的32位并将其转换为int:
~~(2e10) //-1474836480
您可以使用另一个按位运算符并确认它正在获取最低32位来验证这一点:
2e10 & 0xFFFFFFFF // also -1474836480
~~(2e10 & 0xFFFFFFFF) // also -1474836480
Math.floor是为了解释大数而构建的,所以如果大范围内的准确性很重要,那么你应该使用它。
另外值得注意的是:~~正在进行截断,这与仅为正数的地板相同。它不适用于否定:
Math.floor(-2.1) // -3
~~(-2.1) // -2
答案 1 :(得分:2)
正如MDN Docs中所述,我在这里引用,
所有位运算符的操作数都以2的补码格式转换为带符号的32位整数。
这意味着当您将位运算符(例如~
)应用于2.1
时,它首先会转换为整数,然后才会应用运算符。这有效地实现了正数的舍入( floor )效果。
至于为什么使用这些运算符,而不是更好地掌握Math.floor
,有两个主要原因。首先,这些运算符可以considerably faster来实现相同的结果。除了性能,有些人只想要最短的代码。您提到的所有三个运算符都达到了相同的效果,但~~
恰好是最短的,可以说是最容易记住的。
鉴于浮点到整数转换发生在应用按位运算符之前,让我们看看~~
会发生什么。为了简洁,我将使用8位而不是32来表示我们的目标编号(2,从2.1转换后)。
2: 0000 0010
~2: 1111 1101 (-3)
~~2: 0000 0010
所以,你看,我们应用一个运算符只检索整数部分,但我们不能只按一个按位,因为它会弄乱结果。我们使用第二个运算符将其恢复为所需的值。
关于您的上一个示例,请考虑您使用2e + 21测试的数字是一个相对较大的数字。它是2后跟二十一个零。它根本不适合作为32位整数(当你应用按位运算符时,它被转换为的数据类型)。只需看看您的数字与32位有符号整数可以表示的差异。
Max. Integer: 2147483647
2e+21: 2000000000000000000000
二进制怎么样?
Max. Integer: 01111111111111111111111111111111
2e+21: 11011000110101110010011010110111000101110111101010000000000000000000000
相当大,是吗?
真正发生的事情是,Javascript将截断您的大数字,以32位的形式表示。
110110001101011100100110101101110001011 10111101010000000000000000000000
^---- Junk ----^
当我们将截断的数字转换为十进制时,我们会回复您所看到的内容。
Bin: 10111101010000000000000000000000
Dec: -1119879168
相反,Math.floor
说明了大数字并避免截断它们,这是它变慢的可能原因之一,虽然准确。