使用按位或 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】:
它是如何工作的?我们的理论是,使用这样的运算符会强制转换 数字转为整数,从而去掉小数部分
除无符号右移 >>>
之外的所有按位运算都适用于有符号的 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)。【参考方案2】:
这是截断而不是地板。霍华德的回答有点正确。但我要补充一点,Math.floor
完全符合它对负数的预期。从数学上讲,这就是地板。
在您上面描述的情况下,程序员对截断或完全去掉小数更感兴趣。虽然,他们使用的语法有点掩盖了他们将浮点数转换为 int 的事实。
【讨论】:
这是正确答案,接受的不是。再加上Math.floor(8589934591.1)
产生预期结果,8589934591.1 | 0
不。
你是对的乍得。当我测试Math.floor(-5.5)
时,它会返回给我-6
。因此,如果我们按位使用,它将按位使用-5.5 >> 0
,它将返回正确答案-5
【参考方案3】:
在 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
不使用这一事实【参考方案4】:
你的第一点是正确的。该数字被转换为整数,因此任何十进制数字都将被删除。请注意,Math.floor
向负无穷方向舍入到下一个整数,因此在应用于负数时会给出不同的结果。
【讨论】:
【参考方案5】: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);
【讨论】:
【参考方案6】:规范说它被转换为整数:
令 lnum 为 ToInt32(lval)。
性能:之前在jsperf 测试过。
注意:已删除规范的死链接
【讨论】:
【参考方案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 对数字取底的主要内容,如果未能解决你的问题,请参考以下文章