为什么:Math.floor(2e + 21)!= ~~(2e + 21)

时间:2014-10-27 20:08:19

标签: javascript bitwise-operators

我不是按位运算符的专家,但我经常看到一个模式,在比赛中由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

引擎盖下发生了什么?

2 个答案:

答案 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说明了大数字并避免截断它们,这是它变慢的可能原因之一,虽然准确。