2.9999999999999999 >> .5?

2.9999999999999999 >> .5?

提问人:Zen 提问时间:10/6/2008 最后编辑:Jeff AtwoodZen 更新时间:5/10/2022 访问量:2005

问:

我听说您可以将数字右移 .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 位操作 精度

评论

1赞 7/17/2010
只是一个注意事项:可以代替~~2.6Math.floor(2.6)
0赞 Ry- 3/9/2013
当你可以做的时候,为什么要这样做?.50

答:

26赞 John Millikin 10/6/2008 #1

这可能是我见过的最糟糕的想法。它存在的唯一可能目的是赢得一场混淆代码竞赛。你发布的长数字没有任何意义——它们是底层浮点实现的产物,经过天知道有多少中间层过滤。小数字节的位移是疯狂的,我很惊讶它不会引发异常——但这就是 Javascript,总是愿意重新定义“疯狂”。

如果我是你,我会避免使用这个“功能”。它的唯一值是异常错误情况的可能根本原因。使用并怜悯下一个将维护代码的程序员。Math.floor()


证实了我在阅读这个问题时的一些怀疑:

  • 将任何小数右移任何小数都会被截断,给出与彻底混淆读者相同的结果。xyxMath.floor()
  • 2.9999999999999999777955395074968691915...只是可以与“3”区分开来的最大数字。试着单独计算它——如果你向它添加任何内容,它的计算结果将达到 3。这是浏览器和本地系统浮点实现的产物。

评论

0赞 Zen 10/6/2008
我一点也不担心它使代码变得多么模糊。我正在检查我是否可以进一步优化一些东西,我遇到了这个。代码写得有多好完全与我的问题无关。我只是在寻找这个数字如此奇怪的原因。
1赞 DGM 10/6/2008
如果你正在研究这个来优化一些东西,你需要学习更多关于优化的知识,因为你看错了方式或地方。
2赞 olliej 10/6/2008
在当前所有交付的 JS 引擎中(即使您在本地缓存)比简单地执行 bitop 要慢得多(想想数量级)。最快的是.也就是说,这些技巧对于大于 2^32 的值是不安全的,并且大多数在 3~31 时变得不安全。Math.floorfloorsomeNumber | 0
7赞 Jeff Atwood 10/10/2008
“这可能是我见过的最糟糕的想法。我已经告诉过你 999.99999999999999999999999 次不要夸大其词了。
5赞 Rob Walker 10/6/2008 #2

我不认为你的正确转变是相关的。您只是超出了双精度浮点常数的分辨率。

在 Chrome 中:

var x = 2.999999999999999777955395074968691915273666381835937499999;
var y = 2.9999999999999997779553950749686919152736663818359375;

document.write("x=" + x);
document.write(" y=" + y);

打印输出:x = 2.99999999999999996 y=3

6赞 Zach Snow 10/6/2008 #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。

1赞 Curt Hagenlocher 10/6/2008 #4

再加上 John 的回答,它比 Math.floor 性能更高的几率微乎其微。

我不知道 JavaScript 是否使用浮点数或某种无限精度的库,但无论哪种方式,您都会在这样的操作中遇到舍入错误——即使它定义得很好。

评论

0赞 John Millikin 10/6/2008
JS 对所有数值运算都使用 32 位浮点数。可能有任意精度的库可用,但它们未与浏览器捆绑在一起。
0赞 Zen 10/6/2008
事实上,这种技术要慢得多(大约是 Math.floor 的两倍)。我只是想知道为什么这个数字是它是什么,而不是 2.99999999999+1/(2^32) 或 2.99999999999999990000。我开始认为“四舍五入误差”是我得到的最好的答案。
3赞 Matthew Crumley 10/6/2008
只是对 John 的评论稍作更正:JS 数字是 64 位浮点数
3赞 Greg Hewgill 10/6/2008 #5

右移运算符仅对整数(两边)进行运算。因此,向右移动 0.5 位应该完全等同于向右移动 0 位。而且,在移位操作之前,左侧被转换为整数,这与 Math.floor() 的作用相同。

64赞 Ates Goral 10/6/2008 #6

实际上,您只是在第一个操作数上执行 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 分。

评论

0赞 kaya3 3/21/2022
一般来说,截断浮点数并不等同于 - 负数,例如将被截断为 while is ;并且 32 位整数范围之外的数字会溢出,例如 今年 23 岁。Math.floor-1.6-1Math.floor(-1.6)-2(23 + 2 ** 32) >> 0
7赞 Eduardo 10/6/2008 #7

如果您想更深入地了解,请阅读“每个计算机科学家都应该了解的浮点运算知识”:https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

2赞 Joel Coehoorn 10/10/2008 #8

我怀疑将 2.9999999999999999977795539507496869191527366638183593749999999 转换为其二进制表示会很有启发性。它可能与真正的 3 只有 1 位不同。

1赞 aib 10/16/2008 #9

应该注意的是,数字“.0000000000000007779553950749686919152736663818359374”很可能是厄普西隆,定义为“最小的数字 E,使得 (1+E) > 1”。

2赞 TSK 10/30/2008 #10

我怀疑转换 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 区分开来。