使用JavaScript添加一堆浮点数,总和上的错误是什么?

时间:2013-11-10 21:32:46

标签: javascript floating-point

当我用JavaScript添加一堆浮点数时,总和上的误差是多少?应该使用什么误差界来检查两个总和是否相等?

在一个简单的脚本中,我添加了一堆浮点数并比较总和。我注意到有时结果不正确( 相等的两个总和不是)。我在数值分析方面相当薄弱,但即使在审核Is floating point math broken?What Every Computer Scientist Should Know About Floating-Point Arithmetic以及Comparing Floating Point Numbers, 2012 Edition之后,我也很困惑如何最好地比较JavaScript中的浮点数。

首先,我感到困惑:IEEE标准要求加法,减法,乘法和除法的结果完全舍入(就好像它们被精确计算然后四舍五入到最接近的浮点数一样)。如果JavaScript基于IEEE标准,0.1 + 0.2!= 0.3?

怎么样

我想我自己回答了这个问题:我更容易想到基数为10的例子。如果1/3近似为0.333 ... 333且2/3近似为0.666 ... 667,1 / 3 + 1/3 = 0.666 ... 666完全舍入(它是两个近似的精确总和)但是!= 0.666 ... 667。完全舍入操作的中间结果仍然是四舍五入的,这仍然会引入错误。

机器epsilon有多大? JavaScript浮点数显然是64位,显然IEEE双精度格式机epsilon大约是1e-16?

当我添加一堆(n)个浮点数(天真求和,没有成对或Kahan求和)时,总和上的误差是多少?直观地,它与n成比例。我能想到的最坏情况的例子(再次在基数10)是2/3 - 1/3 - 1/3 + 2/3 - 1/3 - 1/3 +等。我认为每次迭代都会增加错误当1为ULP而且总和保持为零时,误差项和相对误差都会无限制地增长?

在“求和误差”部分中,Goldberg更精确(误差项受n *机器epsilon *绝对值之和的约束),但也指出如果总和是以IEEE双精度格式完成的,机器epsilon约为1e-16,因此对于任何合理的n值(n远小于1e16),n *机器epsilon将远小于1。如何使用此错误绑定来检查两个浮点数是否相等?如果它们相等,则1,1s-16,n等之间的关系必须为真?

另一种直觉:如果一堆数字都是正数(我的是),那么虽然误差项可以无限制地增长,但相对误差不会,因为总和必须同时增长。在基数10中,我能想到的最坏情况的例子(其中误差项增长最快而总和增长最慢)是1.000 ... 005近似为1.000 ... 000。重复添加此数字将使误差项增加1/2 ULP(加数,0.000 ... 005),同时将总和增加1个第一位单位。最差的相对误差是4.5 ULP(0.000 ... 045,当总和为9.000 ... 000时),它是(基数-1)/ 2 ULP,它是基数2的1/2 ULP?

如果两个浮点和相等,那么它们的绝对差值必须小于误差界限的两倍,即基数2中的1 ULP?所以在JavaScript中,Math.abs(a - b)< a * 1e-16 + b * 1e-16?

Comparing Floating Point Numbers, 2012 Edition描述了另一种基于相对误差比较浮点数的技术。在JavaScript中,是否可以找到两个浮点数之间可表示数字的数量?

1 个答案:

答案 0 :(得分:2)

连续添加的 n 数之和的最大可能误差与 n 2 成正比,而不是 n

JavaScript由ECMA Language Specification指定,它表示使用IEEE-754 64位二进制浮点并使用舍入到最近模式。我没有看到任何条款允许像某些语言那样提高精确度。

假设所有数字的幅度最多为 b ,其中 b 是一些可表示的值。如果您的数字具有可以更具体地表征的分布,则可能导出比下面描述的更严格的误差。

当一个操作的精确数学结果是 y ,并且没有溢出时,那么使用舍入到最接近模式的IEEE-754二进制浮点数的最大误差是1/2 ULP( y ),其中ULP( y )是幅度上 y 上下两个可表示值之间的距离(使用 y 本身作为“上面”值,如果它是完全可表示的)。这是最大误差,因为 y 总是恰好位于两个边界值之间的中点,或者位于一侧或另一侧,因此从 y 到其中一个的距离是边界值最多是从中点到边界值的距离。

(在IEEE-754 64位二进制中,所有小于2 -1022 的数字的ULP为2 -1074 。所有较大功率的ULP两个是2 -52 倍的数量;例如,2 -52 为1.非幂的2的ULP是两个较小的最大功率的ULP对于任何大于1且小于2的数字,例如2 -52 。)

当添加系列中的前两个数字时,确切结果最多为2 b ,因此该第一次加法中的误差最多为1/2 ULP(2 b) 的)。当添加第三个数字时,结果最多为3 b ,因此该加法中的误差最多为1/2 ULP(3 b b)。到目前为止的总误差最多为1/2(ULP(2 b)+ ULP(3 b))。

此时,加法可以向上舍入,因此到目前为止的部分和可能稍微超过3 b ,并且下一个总和可能稍微超过4 b 。如果我们想要计算错误的严格界限,我们可以使用如下算法:

Let bound = 0.
For i = 2 to n:
    bound += 1/2 ULP(i*b + bound).

也就是说,对于将要执行的每个添加,添加一个误差界限,该误差界限是在给定实际值加上所有先前误差的情况下最大可想到结果的ULP的1/2。 (上面的伪代码需要实现扩展精度或向上舍入以保持数学严谨性。)

因此,只给出要添加的数字的数量和它们的大小的界限,我们可以预先计算误差界限而不事先知道它们的具体值。此错误限制将与 n 2 成比例增长。

如果这个潜在错误太高,有办法减少它:

  • 不是连续添加数字,而是可以将它们分成两半,并且可以添加两半的总和。可以以这种方式递归地对每个半部进行求和。完成此操作后,部分和的最大幅度将更小,因此其误差的界限将更小。例如,连续添加1,我们总和为2,3,4,5,6,7,8,但是,通过这种分裂,我们有2个,2个,2个,2个,然后是4个,4个的并行和,然后8。
  • 我们可以通过添加相互抵消的数字(互补的正数和负数)或首先添加较小的数字来对数字进行排序并保持较小的数量。
  • 可以使用Kahan summation algorithm来获得一些扩展的精确度而无需额外的努力。

考虑一个特定情况:

考虑添加 n 非负数,产生计算的总和 s 。那么 s 中的错误最多( n -1)/ 2•ULP( s )。

证明:每次添加的错误最多为1/2 ULP( x ),其中 x 是计算值。由于我们正在添加非负值,因此累积和不会减少,因此它永远不会超过 s ,并且其ULP最多为 s 的ULP。所以 n -1加法最多产生ULP( s )/ 2的 n -1错误。