为什么Math.cbrt(1728)产生的结果比Math.pow(1728,1 / 3)更准确?

时间:2014-11-15 22:21:40

标签: javascript floating-point

在JavaScript中,Math.cbrt(1728)会评估12的确切结果。

但是,看似等效的表达式Math.pow(1728, 1/3)的计算结果为11.999999999999998

为什么这些结果的精确度不同?

1 个答案:

答案 0 :(得分:18)

前面的一些一般性评论:

  1. 正如本seminal paper所述, 由于有限的精度和范围限制,浮点运算 与实际数学有很大的不同(例如,缺乏 相关性)数学上等价的表达式不是 在浮点运算中进行求值时必须等效。

  2. 计算机语言的标准通常不保证任何标准 数学函数的特殊精度或相同的误差范围 在cbrt()pow()之类的不同数学函数之间。但 数学库为给定的数据提供正确的舍入结果 精确存在,如 CRlibm

  3. 但是,在这种情况下,即使两个函数都针对所有输入正确舍入,cbrt(x)也会提供比pow(x,1.0/3.0)更准确的结果。

    问题是1.0/3.0不能完全表示为浮点数,无论是二进制还是十进制。最接近三分之一的IEEE-754双精度数是3.3333333333333331e-1(或以C / C ++十六进制浮点格式表示时为0x1.5555555555555p-2)。相对代表性误差为-5.5511151231257827e-17(-0x1.0000000000000p-54),意味着1/3的最佳双精度表示略小于所需的数学值。

    pow()的其中一个输入中的初始错误不仅传递到输出,而且由于取幂的误差放大属性而被放大。因此,即使pow(x,1.0/3.0)提供正确的舍入结果,pow()通常也会提供与所需多维数据集根相比过小的结果。对于问题中的示例,正​​确舍入的结果是

    cbrt(1728.0)        = 1.2000000000000000e+1  (0x1.8000000000000p+3)
    pow(1728.0,1.0/3.0) = 1.1999999999999998e+1  (0x1.7ffffffffffffp+3)
    

    也就是说,pow()的结果比cbrt()的结果小ulp。对于幅度较大的论点,差异会大得多。例如,如果x为2 1022 ,则相应结果相差94 ulps:

    x              = 4.4942328371557898e+307  (0x1.0000000000000p+1022)
    cbrt(x)        = 3.5553731598732904e+102  (0x1.965fea53d6e3dp+340)
    pow(x,1.0/3.0) = 3.5553731598732436e+102  (0x1.965fea53d6ddfp+340)
    

    本例中pow()结果的相对误差为1.3108e-14,证明了上述相对误差的放大倍数。

    出于准确性和性能的原因,实现cbrt()因此通常的数学库不会将cbrt(x)映射到pow(x,1.0/3.0),而是使用替代计算方案。虽然实现方式不同,但常用的方法是从初始的低精度近似开始,然后是Halley's method的一个或几个具有立方收敛的步骤。

    根据经验,当计算机语言同时提供专用立方根功能和一般取幂功能时,前者应优先于后者计算立方根。