13

我听说您可以将数字右移 0.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.9999999999999997779553950749686919152736663818359374999999¯(重复 9)。IE中的数字是2.9999999999999997779¯。

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

我一直在尝试找到答案或至少某种模式,但我认为我的问题在于我真的不了解按位运算。我原则上理解这个想法,但是将位序列移动 0.5 对我来说根本没有任何意义。任何帮助表示赞赏。

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

对于 0:0.9999999999999999444888487687421729788184165954589843749¯
对于 1:1.9999999999999999888977697537484345957636833190917968749¯
对于 2-3:x+.99999999999999977795539507496869191527366638183593749¯
对于 4-7:x+.9999999999999995559107901499373838305473327636718749¯
对于 8-15:x+.999999999999999111821580299874767661094665527343749¯
...等等
4

10 回答 10

63

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

于 2008-10-06T03:25:52.543 回答
25

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

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


确认我在阅读问题时的一些怀疑:

  • 将任何小数右移任意小数x只会y简单地截断 truncate ,给出与彻底混淆读者x相同的结果。Math.floor()
  • 2.999999999999999777955395074968691915...只是可以与“3”区分开来的最大数字。尝试自己评估它——如果你添加任何东西,它会评估为 3。这是浏览器和本地系统浮点实现的产物。
于 2008-10-06T02:51:55.780 回答
6

如果您想更深入,请阅读“每个计算机科学家都应该了解的关于浮点运算的知识”:http ://docs.sun.com/source/806-3568/ncg_goldberg.html

于 2008-10-06T03:35:42.370 回答
5

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

在 Chrome 中:

var x = 2.999999999999999777955395074968691915273666381835937499999;
var y = 2.9999999999999997779553950749686919152736663818359375;

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

打印出来:x = 2.9999999999999996 y=3

于 2008-10-06T02:53:08.150 回答
5

试试这个javascript:alert(parseFloat("2.9999999999999997779553950749686919152736663818359374999999"));

然后试试这个: alert(parseFloat("2.9999999999999997779553950749686919152736663818359375"));

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

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

至于为什么右移 0.5 可以做任何事情,似乎 0.5 本身只是预先转换为 int (0)。然后像往常一样,原始浮点数(2.999 ...)通过截断转换为int。

于 2008-10-06T02:53:57.303 回答
3

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

于 2008-10-06T02:59:10.943 回答
2

我怀疑将 2.9999999999999997779553950749686919152736663818359374999999 转换为它的二进制表示会很有启发性。它可能与真正的 3 仅相差 1 位。

于 2008-10-10T14:28:29.293 回答
2

我怀疑将 2.9999999999999997779553950749686919152736663818359374999999 转换为它的二进制表示会很有启发性。它可能与真正的 3 仅相差 1 位。

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

但为什么是 2.9999999999999997779553950749686919152736663818359375

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

哪个高于最后一个可显示单元?四舍五入。转换例程(字符串到数字)简单地被正确编程以将输入四舍五入到下一个浮点数。

2.999999999999999555910790149937383830547332763671875

.......(介于之间的值,增加)->向下舍入

2.9999999999999997779553950749686919152736663818359375

.......(介于之间的值,增加)-> 向上取整到 3

3

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

现在

3 = 11。(二进制)

2.999... = 10.11111111111...(二进制)

所有位均已设置,数字始终为奇数。这意味着精确的半数将被四舍五入,所以你得到了奇怪的.....49999 句号,因为它必须小于精确的一半才能与 3 区分开来。

于 2008-10-30T11:10:41.047 回答
1

为了补充约翰的答案,这比 Math.floor 性能更高的可能性微乎其微。

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

于 2008-10-06T02:54:06.180 回答
1

应该注意的是,数字“.0000000000000007779553950749686919152736663818359374”很可能是Epsilon,定义为“满足 (1+E) > 1 的最小数字 E”。

于 2008-10-15T23:33:05.133 回答