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

moh*_*med 12 javascript floating-point

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

然而,看似等效的表达式Math.pow(1728, 1/3)评估为11.999999999999998.

为什么这些结果的精度会有所不同?

nju*_*ffa 18

前面几个一般性评论:

  1. 正如本篇论文中所解释的,由于有限的精度和范围限制,浮点算法与实际数学(例如,缺乏关联性)有很大的不同,在浮点运算中求值时,数学上等效的表达式不一定是等价的.

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

但是,在这种情况下,即使两个函数都针对所有输入正确舍入,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)
Run Code Online (Sandbox Code Playgroud)

也就是说,结果来自pow()一个小于结果的ulpcbrt().对于幅度较大的论点,差异会大得多.例如,如果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)
Run Code Online (Sandbox Code Playgroud)

pow()例子中的结果的相对误差是1.3108e-14,证明了上述相对误差的放大率.

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

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

  • @lxg:我的专业背景包括微处理器浮点单元和数学库的设计,当我要求重新打开问题时,我已经知道我想写什么. (2认同)