使用按位或 0 对数字取底

Posted

技术标签:

【中文标题】使用按位或 0 对数字取底【英文标题】:Using bitwise OR 0 to floor a number 【发布时间】:2011-11-21 05:24:51 【问题描述】:

我的一位同事偶然发现了一种使用按位或计算浮点数的方法:

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

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

它是如何工作的?我们的理论是使用这样的运算符将数字转换为整数,从而删除小数部分 与Math.floor相比有什么优势吗?也许它有点快? (双关语不是故意的) 它有什么缺点吗?也许在某些情况下它不起作用?清晰度是显而易见的,因为我们必须弄清楚,而且,我正在写这个问题。

谢谢。

【问题讨论】:

缺点:它最多只能工作到 2^31−1,大约是 20 亿 (10^9)。最大数值约为 10^308 btw。 示例:3000000000.1 | 0 计算结果为 -1294967296。所以这种方法不能用于货币计算(尤其是在你乘以 100 以避免十进制数字的情况下)。 @ŠimeVidas Floats 也不应用于货币计算 不是地板,而是截断(向 0 舍入)。 @sequence 尝试在 javascript 控制台中输入 0.1 + 0.2 == 0.3。如果你的语言支持,你应该使用十进制类型。如果没有,请改为存储美分。 【参考方案1】:

你的第一点是正确的。该数字被转换为整数,因此任何十进制数字都将被删除。请注意,Math.floor 向负无穷方向舍入到下一个整数,因此在应用于负数时会给出不同的结果。

【讨论】:

【参考方案2】:

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

除无符号右移 >>> 之外的所有按位运算都适用于有符号的 32 位整数。所以使用按位运算会将浮点数转换为整数。

与 Math.floor 相比,它有什么优势吗?也许有点 快点? (双关语不是故意的)

http://jsperf.com/or-vs-floor/2 似乎稍微快了一点

它有什么缺点吗?也许在某些情况下它不起作用? 清晰度是显而易见的,因为我们必须弄清楚,嗯, 我正在写这个问题。

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

【讨论】:

@harold 确实如此,因为它实际上并不圆整,只是截断。 另一个可能的缺点是Math.floor(NaN) === NaN,而(NaN | 0) === 0。这种差异在某些应用中可能很重要。 由于循环不变的代码运动,您的 jsperf 正在为 chrome 上的空循环生成性能信息。更好的性能测试是:jsperf.com/floor-performance/2 这是asm.js 的标准部分(我第一次了解它的地方)。如果没有其他原因,它会更快,因为它没有调用 Math 对象上的函数,该函数可以随时替换为 Math.floor = function(...) (value | 0) === value 可用于检查一个值实际上是一个整数并且只是一个整数(如链接的 Elm 源代码中的@dwayne-crooks)。 foo = foo | 0 可用于将任何值强制转换为整数(其中 32 位数字被截断,所有非数字变为 0)。【参考方案3】:

这是截断而不是地板。霍华德的回答有点正确。但我要补充一点,Math.floor 完全符合它对负数的预期。从数学上讲,这就是地板。

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

【讨论】:

这是正确答案,接受的不是。再加上Math.floor(8589934591.1) 产生预期结果,8589934591.1 | 0 你是对的乍得。当我测试Math.floor(-5.5) 时,它会返回给我-6。因此,如果我们按位使用,它将按位使用-5.5 >> 0,它将返回正确答案-5【参考方案4】:

规范说它被转换为整数:

令 lnum 为 ToInt32(lval)。

性能:之前在jsperf 测试过。

注意:已删除规范的死链接

【讨论】:

【参考方案5】:

在 ECMAScript 6 中,|0 的等价物是 Math.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

【讨论】:

除了 Math.trunc() 使用大于或等于 2^31 的数字而 | 0 不使用这一事实【参考方案6】:

Javascript 将Number 表示为Double Precision 64-bit Floating numbers。

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 工作,按位的工作方式相同。 如果您在 -2147483647 to 0 工作,按位计算会减 1 个数字。 对于小于-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);

【讨论】:

【参考方案7】:
var myNegInt = -1 * Math.pow(2, 32);
var myFloat = 0.010203040506070809;
var my64BitFloat = myNegInt - myFloat;
var trunc1 = my64BitFloat | 0;
var trunc2 = ~~my64BitFloat;
var trunc3 = my64BitFloat ^ 0;
var trunc4 = my64BitFloat - my64BitFloat % 1;
var trunc5 = parseInt(my64BitFloat);
var trunc6 = Math.floor(my64BitFloat);
console.info(my64BitFloat);
console.info(trunc1);
console.info(trunc2);
console.info(trunc3);
console.info(trunc4);
console.info(trunc5);
console.info(trunc6);

IMO:问题“它是如何工作的?”、“它与 Math.floor 相比有什么优势吗?”、“它有什么劣势吗?”与“将其用于此目的完全合乎逻辑吗?

我认为,在您尝试巧妙地编写代码之前,您可能需要运行这些代码。我的建议;走吧,这里没什么可看的。使用按位来保存一些操作并且对您来说完全重要,通常意味着您的代码架构需要工作。至于为什么它可能有时工作,一个停止的时钟一天两次是准确的,这并不能使它有用。这些运算符有它们的用途,但不是在这种情况下。

【讨论】:

以上是关于使用按位或 0 对数字取底的主要内容,如果未能解决你的问题,请参考以下文章

[HAOI2015] 按位或

按位或「HAOI2015」

BZOJ4036[HAOI2015]按位或 FWT

[HAOI2015]按位或

[HAOI2015]按位或(容斥+前缀和)

位运算符按位与按位或按位非左移右移原码反码补码