在JavaScript中,Math.cbrt(1728)
会评估12
的确切结果。
但是,看似等效的表达式Math.pow(1728, 1/3)
的计算结果为11.999999999999998
。
为什么这些结果的精确度不同?
答案 0 :(得分:18)
前面的一些一般性评论:
正如本seminal paper所述, 由于有限的精度和范围限制,浮点运算 与实际数学有很大的不同(例如,缺乏 相关性)数学上等价的表达式不是 在浮点运算中进行求值时必须等效。
计算机语言的标准通常不保证任何标准
数学函数的特殊精度或相同的误差范围
在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)
也就是说,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的一个或几个具有立方收敛的步骤。
根据经验,当计算机语言同时提供专用立方根功能和一般取幂功能时,前者应优先于后者计算立方根。