2.9999999999999999 >> .5?



我听说你可以将数字右移.5,而不是使用Math.floor()。我决定检查它的极限,以确保它是一个合适的替代品,所以我检查了以下值,并在谷歌Chrome中得到了以下结果:


2.5 >> .5 == 2;
2.9999 >> .5 == 2;
2.999999999999999 >> .5 == 2;  // 15 9s
2.9999999999999999 >> .5 == 3;  // 16 9s

经过一番摆弄,我发现在Chrome和Firefox中,当右移.5时,2的最高可能值为2。999999999999999 777955395074968691915273666381835937499999(9重复)。IE中的数字为2 999999999999999 7779。

我的问题是:000000000000000779553950749686919152736663818359374这个数字的意义是什么?这是一个非常奇怪的数字,它真的激起了我的好奇心。

我一直在努力寻找答案,或者至少找到某种模式,但我认为我的问题在于我真的不理解比特操作。原则上我理解这个想法,但把一个点的顺序改为.5对我来说根本没有任何意义。任何帮助都将不胜感激。

根据记录,奇怪的数字序列会随着2^x而变化。以下仍然正确截断的数字的最高可能值:

对于0:9999999999999999 444888487687421729788184165954589843749对于1:1.9999999999999999 888977697537484345957636833190917968749对于2-3:x+.9999999999999999 77795539507496869191527366638183593749’对于4-7:x+.9999999999999999 5559107901499373838305473327636718749对于8-15:x+.9999999999999999 11182158029987476766109496527343749等等

实际上,您只需对第一个操作数执行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.999999999999999。现在尝试再添加一个9:

alert(2.9999999999999999);

你会得到3分。

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

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


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

  • 将任何分数x右移任意分数y将简单地截断x,给出与Math.floor()相同的结果,同时彻底混淆读者
  • 2.999999999999999 777955395074968691915…只是可以与"3"区分的最大数字。试着自己评估它——如果你向它添加任何内容,它的评估结果将为3。这是浏览器和本地系统浮点实现的产物

如果你想深入了解,请阅读"每一位计算机科学家都应该知道的浮点运算":https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

试试这个javascript:警报(parseFloat("2.999999999999999 777955395074968691915273666381835937499999");

然后试试这个:警报(parseFloat("2.999999999999999 7779553950749686919152736663818359375");

您所看到的只是简单的浮点不准确性。有关这方面的更多信息,请参见以下示例:http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems.

基本问题是,浮点值最接近表示第二个数字的值大于或等于3,而浮点值最靠近第一个数字的距离严格小于3。

至于为什么右移0.5会产生任何理智的结果,似乎0.5本身就是事先转换为int(0)。然后,像往常一样,通过截断将原始浮点值(2.999…)转换为int。

我不认为你的右移是相关的。您只是超出了双精度浮点常量的分辨率。

在Chrome中:

var x = 2.999999999999999777955395074968691915273666381835937499999;
var y = 2.9999999999999997779553950749686919152736663818359375;
document.write("x=" + x);
document.write(" y=" + y);

打印输出:x=2.9999999999999996 y=3

右移运算符仅对整数(两侧)进行运算。所以,右移0.5位应该正好等于右移0位。并且,在移位操作之前,左手边被转换为整数,这与Math.floor()的作用相同。

我怀疑转换2.999999999997777955395074968691915273666381835937499999对它的二元表示将是有启发性的。可能只有一点不同从true 3。

很好的猜测,但没有雪茄。由于双精度FP数有53位,因此3之前的最后一个FP数实际上是(精确):2.9999999999999995555910790149937383830547332763671875

但为什么2.9999999999999997779553950749686919152736663818359375

(这是准确的,不是49999…!)

哪个比最后一个可显示单元高?四舍五入。转换例程(字符串到数字)只需正确编程,即可将输入四舍五入到下一个浮点数字。

2.9999999999999995555910790149937383830547332763671875

(值介于之间,增加)->四舍五入

2.9999999999999997779553950749686919152736663818359375

(值介于之间,增加)->四舍五入至3

3

转换输入必须使用全精度。如果数字正好是这两个fp编号(即2.9999999999999999 7779553950749686919152736663818359375)四舍五入取决于设置的标志。默认舍入为四舍五入到偶数,这意味着该数字将舍入到下一个偶数。

现在

3=11。(二进制)

2.999…=10.1111111111111……(二进制)

所有位都已设置,数字总是奇数。这意味着确切的一半数字将被四舍五入,所以你得到了奇怪的。。。。。49999周期,因为它必须小于精确的一半才能与3区分开。

我怀疑将2.99999999999777955395074968691915273666381835937499999转换为其二进制表示会很有启发性。这可能与真正的3只有一点不同。

再加上约翰的答案,这比Math.floor更具表现力的可能性微乎其微。

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

需要注意的是,数字"000000000000000 7779553950749686919152736663818359374"很可能是Epsilon,定义为"最小的数字E,使得(1+E)>1。"

最新更新