Javascript 实现精确的cbrt()函数而无需额外的精度
在JavaScript中,没有本机的Javascript 实现精确的cbrt()函数而无需额外的精度,javascript,algorithm,floating-point,Javascript,Algorithm,Floating Point,在JavaScript中,没有本机的cbrt方法可用。理论上,您可以使用如下方法: function cbrt(x) { return Math.pow(x, 1 / 3); } 然而,这失败了,因为数学中的恒等式不一定适用于浮点运算。例如,使用二进制浮点格式无法准确表示1/3 此操作失败的示例如下: cbrt(Math.pow(4, 3)); // 3.9999999999999996 随着数量的增加,情况会变得更糟: cbrt(Math.pow(165140, 3)); // 165
cbrt
方法可用。理论上,您可以使用如下方法:
function cbrt(x) {
return Math.pow(x, 1 / 3);
}
然而,这失败了,因为数学中的恒等式不一定适用于浮点运算。例如,使用二进制浮点格式无法准确表示1/3
此操作失败的示例如下:
cbrt(Math.pow(4, 3)); // 3.9999999999999996
随着数量的增加,情况会变得更糟:
cbrt(Math.pow(165140, 3)); // 165139.99999999988
是否有任何算法能够将立方根值计算到几个ULP内(如果可能,最好是1 ULP)
这个问题与类似,但请记住,JavaScript没有任何更高精度的数字类型(JavaScript中只有一种数字类型),也没有内置的cbrt
函数
- 对于
可以是任意的,但是log,exp
直接在大多数FPU上实现2
- 现在你除以3。。。3.0用FP精确表示
(e=~x整数部分位计数的1/3)
尾数=0
和指数=e
)y
- 将位切换为1
- 如果
将位切换回零(y*y*y>x)
y
转换为float
只是复制尾数位并设置指数
请参见您可以将现有实现(如)移植到Javascript。该代码有两种变体,一种是更精确的迭代变体,另一种是非迭代变体 Ken Turkowski的实现依赖于将半径和分解为尾数和指数,然后重新组合,但这仅用于通过强制执行介于-2和0之间的二元指数,使其在1/8和1之间的一次近似范围内。在Javascript中,您可以通过反复除以或乘以8来实现这一点,这应该不会影响精度,因为这只是一个指数移位 本文中所示的实现对于单精度浮点数字是准确的,但是Javascript使用双精度数字。再加上两次牛顿迭代会产生很好的精度 下面是所描述的
cbrt
算法的Javascript端口:
Math.cbrt = function(x)
{
if (x == 0) return 0;
if (x < 0) return -Math.cbrt(-x);
var r = x;
var ex = 0;
while (r < 0.125) { r *= 8; ex--; }
while (r > 1.0) { r *= 0.125; ex++; }
r = (-0.46946116 * r + 1.072302) * r + 0.3812513;
while (ex < 0) { r *= 0.5; ex++; }
while (ex > 0) { r *= 2; ex--; }
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
return r;
}
Math.cbrt=函数(x)
{
如果(x==0)返回0;
if(x<0)返回-Math.cbrt(-x);
var r=x;
var-ex=0;
而(r<0.125){r*=8;ex--;}
而(r>1.0){r*=0.125;ex++;}
r=(-0.46946116*r+1.072302)*r+0.3812513;
而(ex<0){r*=0.5;ex++;}
而(ex>0){r*=2;ex--;}
r=(2.0/3.0)*r+(1.0/3.0)*x/(r*r);
r=(2.0/3.0)*r+(1.0/3.0)*x/(r*r);
r=(2.0/3.0)*r+(1.0/3.0)*x/(r*r);
r=(2.0/3.0)*r+(1.0/3.0)*x/(r*r);
返回r;
}
我没有对它进行过广泛的测试,特别是在定义不好的情况下,但是与pow
的测试和比较看起来还不错。性能可能不太好。已添加到ES6/ES2015规范中,因此至少要首先检查是否定义了它。它可以像这样使用:
Math.cbrt(64)//4
而不是
Math.pow(64,1/3);//3.999999999999999 6
您的问题非常接近。我只能重申我在思考时发现的诀窍,虽然1/3不能表示为double,但3是,因此您可以pow(候选者,3)
并对照原始值检查(假设pow()
质量良好)(理想情况下,您会有一个正确的四舍五入pow_ru()
还有一点额外的精确性)。@PascalCuoq:确实很接近。关于这个问题的唯一一点是,它似乎与C99有关,C99有一些选项,比如长双倍,如果我没有错的话,可以获得更高的精度。关于JS的一点是,由于其单一的数字类型,它没有额外的精度,这使得事情变得更加困难。1-文献中描述了pow()
的一些实现,如exp()
和log()
的组合,但是,这些描述指出,如果您希望最终结果合理准确,那么中间结果需要额外的精度。事实上,我认为我看到的对这种pow()实现的描述是在对8087扩展双精度格式及其64位精度的讨论中,这使得这种方法适用于双精度pow()
.2-您不需要从结果的粗略近似开始,其中只有指数是正确的。您可以使用pow(x,1.0/3.0)
作为起始点。@Pascal Cuoq i8087在内部使用80位,如果我没记错的话,您可以使用它作为起始点80位是8087扩展格式的总大小。其中,15用于指数,1用于符号,留下64位精度。无论如何,问题是在Javascript的上下文中,它不提供对任何扩展格式的访问。@PascalCuoq:“您可以使用pow(x,1.0/3.0)作为起点”。仅适用于正x
。例如,-27^(1/3.0)是nan
。
Math.cbrt = function(x)
{
if (x == 0) return 0;
if (x < 0) return -Math.cbrt(-x);
var r = x;
var ex = 0;
while (r < 0.125) { r *= 8; ex--; }
while (r > 1.0) { r *= 0.125; ex++; }
r = (-0.46946116 * r + 1.072302) * r + 0.3812513;
while (ex < 0) { r *= 0.5; ex++; }
while (ex > 0) { r *= 2; ex--; }
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
r = (2.0 / 3.0) * r + (1.0 / 3.0) * x / (r * r);
return r;
}