【问题标题】:Midpoint 'rounding' when dealing with large numbers?处理大数时的中点“四舍五入”?
【发布时间】:2014-06-24 23:35:18
【问题描述】:

所以我试图理解 JavaScript 在处理大数字时的行为。考虑以下(在 Firefox 和 Chrome 中测试):

console.log(9007199254740993) // 9007199254740992
console.log(9007199254740994) // 9007199254740994
console.log(9007199254740995) // 9007199254740996
console.log(9007199254740996) // 9007199254740996
console.log(9007199254740997) // 9007199254740996
console.log(9007199254740998) // 9007199254740998
console.log(9007199254740999) // 9007199254741000

现在,我知道为什么它会输出“错误”数字——它试图将它们转换为浮点表示形式,并且正在四舍五入到最接近的可能浮点值——但我不完全确定为什么会这样选择这些特定的数字。我的猜测是它试图四舍五入到最接近的“偶数”,并且由于 9007199254740996 可以被 4 整除,而 9007199254740994 不是,它认为 9007199254740996 更“偶数”。

  • 它使用什么算法来确定内部表示?我的猜测是它是常规中点舍入的扩展(舍入到偶数是 IEEE 754 函数中的默认舍入模式)。
  • 此行为是作为 ECMAScript 标准的一部分指定的,还是依赖于实现?

【问题讨论】:

  • @RobG:评估console.log(9007199254740993) 涉及两次转换:一次将 JavaScript 代码中的十进制文字转换为内部二进制浮点格式,第二次从内部二进制格式转换为十进制执行log 方法时用于控制台输出的字符串。这些转换中的每一个都需要相当复杂的算法(如果做得好的话)。所以说没有算法是不正确的。
  • 是的,我认为这不是舍入算法。
  • 是的,但舍入是算法的重要组成部分:十进制到二进制的转换必须决定如何将精确的十进制值转换为 IEEE 754 binary64 格式可表示的最接近的数字。正如@PatriciaShanahan 所描述的那样,该决定的一部分涉及选择哪种方式进行舍入,并且通常的约定是使用舍入到偶数。
  • 嗯。事实证明,这不仅仅是“常规约定”:ECMA-262 标准明确规定应使用 round-ties-to-even 舍入方法(在第 8.5 节中)。

标签: javascript floating-point floating-point-precision floating-point-conversion


【解决方案1】:

正如 Mark Dickinson 在对该问题的评论中所指出的,ECMA-262 ECMAScript 语言规范要求使用 IEEE 754 64 位二进制浮点来表示 Number Type。相关的舍入规则是“选择该集合中值最接近 x 的成员。如果该集合的两个值同样接近,则选择具有偶数有效数的那个......”。

这些规则是通用的,适用于算术的舍入结果以及文字的值。

以下是问题的相关范围内的所有数字,这些数字可以在 IEEE 754 64 位二进制浮点中精确表示。每个都显示为其十进制值,也显示为其位模式的十六进制表示。具有偶数有效数的数字在其位模式中有一个偶数最右边的十六进制数字。

9007199254740992 bit pattern 0x4340000000000000
9007199254740994 bit pattern 0x4340000000000001
9007199254740996 bit pattern 0x4340000000000002
9007199254740998 bit pattern 0x4340000000000003
9007199254741000 bit pattern 0x4340000000000004

每个偶数输入都是这些数字之一,并四舍五入到该数字。每个奇数输入正好是它们两个之间的一半,并四舍五入到具有偶数有效位的那个。这导致奇数输入四舍五入为 9007199254740992、9007199254740996 和 9007199254741000。

【讨论】:

    【解决方案2】:

    Patricia Shanahan 的 answer 帮助很大,并解释了我的主要问题。然而,对于问题的第二部分——这种行为是否依赖于实现——事实证明是的,但与我最初想象的方式略有不同。引用ECMA-262 5.1 § 7.8.3:

    ...四舍五入的值必须是 MV 的 Number 值(如 8.5 中所指定),除非字面量是 DecimalLiteral 并且字面量有超过 20 个有效数字,在这种情况下 Number value 可以是通过将 20 日之后的每个有效数字替换为 0 数字而产生的文字的 MV 的 Number 值,也可以是通过将之后的每个有效数字替换而产生的文字的 MV 的 Number 值第 20 个带有 0 数字,然后在第 20 个有效数字位置增加文字。

    换句话说,实现可能会选择忽略第 20 位之后的所有内容。考虑一下:

    console.log(9007199254740993.00001)
    

    Chrome 和 Firefox 都会输出9007199254740994,但是,Internet Explorer 会输出9007199254740992,因为它选择忽略第 20 位之后的数字。有趣的是,这似乎不是符合标准的行为(至少在我阅读此标准时)。它应该将其解释为与 9007199254740993.0001 相同,但事实并非如此。

    【讨论】:

      【解决方案3】:

      JavaScript 将数字表示为 64 位浮点值。这是在标准中定义的。

      http://en.wikipedia.org/wiki/Double-precision_floating-point_format

      所以没有什么与中点舍入有关。

      作为提示,每个 32 位整数都有一个双精度浮点格式的精确表示。


      好的,既然您要的是确切的算法,我检查了 Chrome 的 V8 引擎是如何做到的。 V8定义了一个StringToDouble函数,在如下文件中调用InternalStringToDouble:

      https://github.com/v8/v8/blob/master/src/conversions-inl.h#L415

      这反过来又调用了那里定义的Strotd函数:

      https://github.com/v8/v8/blob/master/src/strtod.cc

      【讨论】:

      • 64 位浮点数最多可以保持 15 位有效数字。 9007199254740995 是 16 位数字,因此没有精确表示。
      • @LucasTrzesniewski 很好地找到了源代码。然而,即使通过算术(而不是字符串转换)得出数字也会给出相同的舍入。例如。 393447746243*22893 和 950026289921*9481 都返回 x=9007199254741000,即使真正的答案分别是 (x-1) 和 (x+1)。我也不知道发生了什么;只是为拼图提供另一块。
      • @Matt 就像我说的那样 9007199254741000 高于双精度的最大精度,所以你不能真正期望那里的实际值。我刚刚检查过,您提供的两个乘法在 C# 中给出的值与在 JS 中的值相同,所以我想这就是 CPU 在硬件中进行计算的方式。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多