Javascript 实现精确的cbrt()函数而无需额外的精度

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

在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)); // 165139.99999999988
是否有任何算法能够将立方根值计算到几个ULP内(如果可能,最好是1 ULP)

这个问题与类似,但请记住,JavaScript没有任何更高精度的数字类型(JavaScript中只有一种数字类型),也没有内置的
cbrt
函数

  • 您可以将公式用于

    • 对于
      log,exp
      可以是任意的,但是
      2
      直接在大多数FPU上实现
    • 现在你除以3。。。3.0用FP精确表示
  • 或者您可以使用位搜索

  • 查找输出的指数
    (e=~x整数部分位计数的1/3)
  • 创建适当的固定数字y(
    尾数=0
    指数=e
  • y

    • 将位切换为1
    • 如果
      (y*y*y>x)
      将位切换回零
  • 使用下一位循环#3(在LSB后停止)

  • 二进制搜索的结果尽可能精确(没有其他方法能比得上它)。。。为此,您需要一点尾数计数迭代。您必须使用FP进行计算,因此将
    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;
    }