提问人:Zen 提问时间:10/6/2008 最后编辑:Jeff AtwoodZen 更新时间:5/10/2022 访问量:2005
2.9999999999999999 >> .5?
2.9999999999999999 >> .5?
问:
我听说您可以将数字右移 .5 而不是使用 Math.floor()。我决定检查它的限制以确保它是合适的替代品,因此我检查了以下值并在 Google Chrome 中获得了以下结果:
2.5 >> .5 == 2;
2.9999 >> .5 == 2;
2.999999999999999 >> .5 == 2; // 15 9s
2.9999999999999999 >> .5 == 3; // 16 9s
经过一番摆弄,我发现在 Chrome 和 Firefox 中,当右移 0.5 时,将产生 2 的 2 的最大可能值是 2.99999999999999997779553950749686919152736663818359374999999 ̄(9 重复)。IE中的数字是2.999999999999999997779。
我的问题是:.0000000000000007779553950749686919152736663818359374这个数字有什么意义?这是一个非常奇怪的数字,它确实激起了我的好奇心。
我一直在试图找到一个答案,或者至少是某种模式,但我认为我的问题在于我真的不理解按位运算。我原则上理解这个想法,但将位序列移动 .5 对我来说根本没有任何意义。任何帮助都是值得赞赏的。
作为记录,奇怪的数字序列随着 2^x 的变化而变化。仍可正确截断的以下数字的最高可能值:
for 0: 0.9999999999999999444888487687421729788184165954589843749¯ for 1: 1.9999999999999999888977697537484345957636833190917968749¯ for 2-3: x+.99999999999999977795539507496869191527366638183593749¯ for 4-7: x+.9999999999999995559107901499373838305473327636718749¯ for 8-15: x+.999999999999999111821580299874767661094665527343749¯ ...and so forth
答:
这可能是我见过的最糟糕的想法。它存在的唯一可能目的是赢得一场混淆代码竞赛。你发布的长数字没有任何意义——它们是底层浮点实现的产物,经过天知道有多少中间层过滤。小数字节的位移是疯狂的,我很惊讶它不会引发异常——但这就是 Javascript,总是愿意重新定义“疯狂”。
如果我是你,我会避免使用这个“功能”。它的唯一值是异常错误情况的可能根本原因。使用并怜悯下一个将维护代码的程序员。Math.floor()
证实了我在阅读这个问题时的一些怀疑:
- 将任何小数右移任何小数都会被截断,给出与彻底混淆读者相同的结果。
x
y
x
Math.floor()
- 2.9999999999999999777955395074968691915...只是可以与“3”区分开来的最大数字。试着单独计算它——如果你向它添加任何内容,它的计算结果将达到 3。这是浏览器和本地系统浮点实现的产物。
评论
Math.floor
floor
someNumber | 0
我不认为你的正确转变是相关的。您只是超出了双精度浮点常数的分辨率。
在 Chrome 中:
var x = 2.999999999999999777955395074968691915273666381835937499999;
var y = 2.9999999999999997779553950749686919152736663818359375;
document.write("x=" + x);
document.write(" y=" + y);
打印输出:x = 2.99999999999999996 y=3
试试这个javascript: alert(parseFloat(“2.9999999999999999779553950749686919152736663818359374999999”));
然后试试这个: alert(parseFloat(“2.9999999999999999779553950749686919152736663818359375”));
您看到的是简单的浮点不准确。有关此内容的详细信息,请参阅以下内容,例如:http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems。
基本问题是,浮点值可以达到的最接近于表示第二个数字的大于或等于 3,而浮点数可以达到的第一个数字的收盘价严格小于 3。
至于为什么右移 0.5 会做任何理智的事情,似乎 0.5 本身只是事先转换为 int (0)。然后,像往常一样,原始浮点数 (2.999...) 通过截断转换为 int。
再加上 John 的回答,它比 Math.floor 性能更高的几率微乎其微。
我不知道 JavaScript 是否使用浮点数或某种无限精度的库,但无论哪种方式,您都会在这样的操作中遇到舍入错误——即使它定义得很好。
评论
右移运算符仅对整数(两边)进行运算。因此,向右移动 0.5 位应该完全等同于向右移动 0 位。而且,在移位操作之前,左侧被转换为整数,这与 Math.floor() 的作用相同。
实际上,您只是在第一个操作数上执行 floor(),而没有任何浮点运算。由于左移和右移位位运算仅对整数操作数有意义,因此 JavaScript 引擎首先将两个操作数转换为整数:
2.999999 >> 0.5
成为:
Math.floor(2.999999) >> Math.floor(0.5)
这反过来又是:
2 >> 0
移位 0 位意味着“不要移位”,因此您最终会得到第一个操作数,只是被截断为整数。
SpiderMonkey 源代码有:
switch (op) {
case JSOP_LSH:
case JSOP_RSH:
if (!js_DoubleToECMAInt32(cx, d, &i)) // Same as Math.floor()
return JS_FALSE;
if (!js_DoubleToECMAInt32(cx, d2, &j)) // Same as Math.floor()
return JS_FALSE;
j &= 31;
d = (op == JSOP_LSH) ? i << j : i >> j;
break;
您看到某些数字的“四舍五入”是由于 JavaScript 引擎无法处理超过特定精度的十进制数字,因此您的数字最终会四舍五入到下一个整数。在浏览器中尝试以下操作:
alert(2.999999999999999);
你会得到 2.9999999999999999。现在尝试再添加一个 9:
alert(2.9999999999999999);
你会得到 3 分。
评论
Math.floor
-1.6
-1
Math.floor(-1.6)
-2
(23 + 2 ** 32) >> 0
如果您想更深入地了解,请阅读“每个计算机科学家都应该了解的浮点运算知识”:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
我怀疑将 2.9999999999999999977795539507496869191527366638183593749999999 转换为其二进制表示会很有启发性。它可能与真正的 3 只有 1 位不同。
应该注意的是,数字“.0000000000000007779553950749686919152736663818359374”很可能是厄普西隆,定义为“最小的数字 E,使得 (1+E) > 1”。
我怀疑转换 2.99999999999999997779553950749686919152736663818359374999999 它的二进制表示将具有启发性。它可能只有 1 点不同 从 true 3.
猜得好,但没有雪茄。 由于双精度 FP 数有 53 位,因此 3 之前的最后一个 FP 数实际上是 (精确):2.9999999999999999555910790149937383830547332763671875
但为什么会这样 2.9999999999999997779553950749686919152736663818359375
(这是确切的,不是 49999 ...... !
哪个比最后一个可显示单位高?舍入。转换例程(字符串到数字)只需正确编程即可将输入四舍五入到下一个浮点数。
2.999999999999999555910790149937383830547332763671875
.......(值之间,递增)->向下舍入
2.9999999999999997779553950749686919152736663818359375
.......(值介于、递增)->舍入为 3
3
转换输入必须使用全精度。如果数字正好是两者之间的一半 这两个 FP 编号(即 2.9999999999999999779553950749686919152736663818359375) 舍入取决于设置的标志。默认舍入为四舍五入为偶数,这意味着数字将四舍五入到下一个偶数。
现在
3 = 11。(二进制)
2.999... = 10.111111111111......(二进制)
所有位都已设置,数字始终为奇数。这意味着确切的半数将被四舍五入,所以你会得到奇怪的......49999 周期,因为它必须小于正好的一半才能与 3 区分开来。
评论
~~2.6
Math.floor(2.6)
.5
0