在没有额外精度的情况下实现精确的cbrt()函数

时间:2014-05-01 05:16:07

标签: 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)); // 165139.99999999988

是否有任何算法能够计算出几个ULP内的立方根值(如果可能,最好是1个ULP)?

这个问题类似于Computing a correctly rounded / an almost correctly rounded floating-point cubic root,但请记住,JavaScript没有任何更高精度的数字类型可供使用(JavaScript中只有一种数字类型),也没有内置的在cbrt函数中开始。

3 个答案:

答案 0 :(得分:2)

您可以将现有实施(例如this one in C)移植到Javascript。该代码有两个变体,一个更准确的迭代和一个非交互的变体。

Ken Turkowski的实现依赖于将radicand拆分为尾数和指数然后重组它,但这仅用于通过在-2之间强制执行二进制指数将其置于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;
}

我没有对它进行过广泛的测试,特别是在非常明确的角落情况下,但测试和与pow的比较我看起来没问题。表现可能不是那么好。

答案 1 :(得分:1)

Math.cbrt已添加到ES6 / ES2015规范中,因此至少首先检查它是否已定义。它可以像:

一样使用

Math.cbrt(64); //4

而不是

Math.pow(64, 1/3); // 3.9999999999999996

答案 2 :(得分:0)

  1. 您可以使用pow computation

    的公式
    x^y     = exp2(y*log2(x))
    x^(1/3) = exp2(log2(x)*1/3)
            = exp2(log2(x)/3)
    
    • log,exp的基数可以是任意数据,但2直接在大多数FPU上实现
    • 现在你除以3 ......并且精确地用FP表示3.0。
  2. 或您可以使用位搜索

    1. 找到输出(e= ~1/3 of integer part bit count of x)
    2. 的指数
    3. 创建适当的固定数字y(mantissa=0exponent=e
    4. y

      MSB 位开始搜索bin
      • 将位切换为一个
      • 如果(y*y*y>x)将位切换回零
    5. 循环#3 与下一位(在 LSB 之后停止)

    6. 二进制搜索的结果尽可能精确(没有其他方法可以击败它)...你需要尾数位计数迭代。您必须使用 FP 进行计算,因此将y转换为float只是复制尾数位并设置指数。

      请参阅pow on integer arithmetics in C++