使用按位或0将数字取底


192

我的一位同事偶然发现了一种使用按位或(

var a = 13.6 | 0; //a == 13

我们正在谈论它,并且想知道一些事情。

  • 它是如何工作的?我们的理论是使用这样的运算符将数字转换为整数,从而除去小数部分
  • 这样做有什么好处Math.floor吗?也许更快一点?(双关语无意)
  • 有什么缺点吗?也许在某些情况下不起作用?清晰度很明显,因为我们必须弄清楚这一点,我在写这个问题。

谢谢。


6
缺点:它最多只能工作2 ^ 31−1,大约20亿(10 ^ 9)。最大数字值约为10 ^ 308 btw。
森那维达斯

12
示例:3000000000.1 | 0计算结果为-1294967296。因此,该方法不能用于金钱计算(尤其是在您乘以100以避免十进制数的情况下)。
森那维达斯

13
@ŠimeVidasFloats也不应用于金钱计算
George Reith

20
它不是地板,它是截断的(向0舍入)。
巴特洛梅耶扎莱夫斯基

3
@sequence尝试0.1 + 0.2 == 0.3在JavaScript控制台中输入。如果您的语言支持,则应使用十进制类型。如果不是,请存储分。
Alex Turpin

Answers:


160

它是如何工作的?我们的理论是使用这样的运算符将数字转换为整数,从而除去小数部分

除无符号右移以外的所有按位运算都>>>对有符号的32位整数起作用。因此,使用按位运算会将浮点数转换为整数。

与做Math.floor相比有什么优势吗?也许更快一点?(双关语无意)

http://jsperf.com/or-vs-floor/2似乎速度更快

有什么缺点吗?也许在某些情况下不起作用?清晰度很明显,因为我们必须弄清楚这一点,我在写这个问题。

  • 不会通过jsLint。
  • 仅32位有符号整数
  • 比较行为:Math.floor(NaN) === NaN,而(NaN | 0) === 0

9
@harold确实是因为它实际上没有舍入而只是被截断了。
亚历克斯·图尔平

5
另一个可能的缺点是Math.floor(NaN) === NaN,而(NaN | 0) === 0。这种差异在某些应用程序中可能很重要。
Ted Hopp

4
由于循环不变的代码运动,您的jsperf会产生chrome上空循环的性能信息。更好的性能测试将是:jsperf.com/floor-performance/2
Sam Giles

4
这是asm.js(我第一次了解它的)标准部分。如果没有其他原因,它会更快,因为它没有在Math对象上调用一个函数,该函数随时都可以像中那样被替换Math.floor = function(...)
gman

3
(value | 0) === value可以用来检查值实际上是一个整数,而只是一个整数(如Elm源代码@ dwayne-crooks链接)。并且foo = foo | 0可用于将任何值强制转换为整数(其中32位数字被截断而所有非数字均变为0)。
大卫·迈克尔·格雷格

36

这是截断,而不是地板。霍华德的答案是正确的。但是我要补充Math.floor一下,它确实可以实现负数方面的功能。从数学上讲,这就是地板。

在上述情况下,程序员对截断或完全舍去小数点更感兴趣。虽然,他们使用的语法有点掩盖了他们将float转换为int的事实。


7
这是正确的答案,接受的不是。添加到它Math.floor(8589934591.1)产生预期的结果,8589934591.1 | 0
Salman A

21

在ECMAScript中6,相当于|0Math.trunc,善良的我应该说:

通过删除任何小数位来返回数字的整数部分。无论参数是正数还是负数,它都只会截断点和其后的数字。

Math.trunc(13.37)   // 13
Math.trunc(42.84)   // 42
Math.trunc(0.123)   //  0
Math.trunc(-0.123)  // -0
Math.trunc("-1.123")// -1
Math.trunc(NaN)     // NaN
Math.trunc("foo")   // NaN
Math.trunc()        // NaN

6
除了Math.trunc()数字大于或等于2 ^ 31 | 0且不的
事实

10

您的第一点是正确的。该数字被强制转换为整数,因此将删除所有十进制数字。请注意,Math.floor四舍五入到负无穷大的下一个整数,因此当应用于负数时会得出不同的结果。


5

Javascript表示NumberDouble Precision 64位浮点数

Math.floor 考虑到这一点。

按位运算以32位带符号整数工作。32位带符号整数将第一位用作负号,其他31位为数字。因此,允许的最小和最大数字32位带符号数字分别为-2,147,483,648和2147483647(0x7FFFFFFFF)。

所以当你做的时候| 0,本质上你就是在做& 0xFFFFFFFF。这意味着,任何表示为0x80000000(2147483648)或更大的数字都将作为负数返回。

例如:

 // Safe
 (2147483647.5918 & 0xFFFFFFFF) ===  2147483647
 (2147483647      & 0xFFFFFFFF) ===  2147483647
 (200.59082098    & 0xFFFFFFFF) ===  200
 (0X7FFFFFFF      & 0xFFFFFFFF) ===  0X7FFFFFFF

 // Unsafe
 (2147483648      & 0xFFFFFFFF) === -2147483648
 (-2147483649     & 0xFFFFFFFF) ===  2147483647
 (0x80000000      & 0xFFFFFFFF) === -2147483648
 (3000000000.5    & 0xFFFFFFFF) === -1294967296

也。按位操作不会“落地”。它们截断,这与说的一样,它们最接近0。一旦你到处去负数,Math.floor几轮下来,而按位开始舍去

就像我之前说的那样,Math.floor它更安全,因为它使用64位浮点数。按位速度更快,是的,但仅限于32位带符号范围。

总结一下:

  • 如果从开始工作,则按位工作原理相同0 to 2147483647
  • 如果您从开始工作,则按位计算将减少1 -2147483647 to 0
  • 对于小于-2147483648和大于的数字,按位运算完全不同2147483647

如果您真的想调整性能并同时使用两者:

function floor(n) {
    if (n >= 0 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    if (n > -0x80000000 && n < 0) {
      return (n - 1) & 0xFFFFFFFF;
    }
    return Math.floor(n);
}

只是添加Math.trunc像按位运算的作品。因此,您可以执行以下操作:

function trunc(n) {
    if (n > -0x80000000 && n < 0x80000000) {
      return n & 0xFFFFFFFF;
    }
    return Math.trunc(n);
}

5
  • 规范说它被转换为整数:

    令lnum为ToInt32(lval)。

  • 性能:之前已经在jsperf上进行了测试。

注意:规范的无效链接已删除

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.