计算正确舍入/几乎正确舍入的浮点立方根

时间:2013-08-05 17:08:40

标签: algorithm floating-point ieee-754

假设可以使用CRlibm中找到的正确舍入的标准库函数。那么如何计算双精度输入的正确圆角立方根?

引用常见问题解答时,这个问题不是“[我]面临的实际问题”。这有点像家庭作业。但立方根是经常发现的操作,可以想象这个问题是某人面临的实际问题。

由于“最好的Stack Overflow问题中有一些源代码”,这里有一些源代码:

  y = pow(x, 1. / 3.);

以上不计算正确的圆角立方根,因为1/3不能完全表示为double


附加说明:

article描述了如何计算浮点立方根,但推荐的Newton-Raphson算法的最后一次迭代必须以更高的精度完成算法才能计算出正确的舍入双精度立方根。这可能是计算它的最佳方式,但我仍然在寻找一种利用现有正确舍入标准化函数的捷径。

C99包含cbrt()函数,但不能指望所有编译器都正确舍入or even faithful。 CRlibm的设计者本可以选择在提供的函数列表中包含cbrt(),但他们没有。可以参考其他正确舍入的数学函数库中可用的实现。

3 个答案:

答案 0 :(得分:5)

鉴于曲线x = y ^ 3上有很多易于计算的有理点,我很想减少s ^ 3~x,s理性且只有几位宽。然后你有:

cbrt(x) = s * cbrt(1 + (x - s^3)/s)

显然,使用您最喜欢的系列近似来评估校正项,并通过头尾FMA算法计算残差,如果需要,可以通过ulp向上或向下压缩结果(您不需要最多的完整计算)当时,显然)。

这不完全符合问题的精神,但绝对可以使用,并且很容易通过这种方式证明必要的界限。希望其他人可以提出更聪明的建议(我已经用完了这个月的聪明才智)。

答案 1 :(得分:2)

我担心我不知道如何保证正确舍入的双精度立方根,但可以提供一个非常接近正确舍入的问题。换句话说,最大误差似乎非常接近0.5 ulp。

Peter Markstein," IA-64和基本功能:速度和精度" (Prentice-Hall 2000)

提出了有效的基于FMA的技术,用于正确地舍入倒数平方根,倒数平方根和倒数平方根,但在这方面它并未涵盖立方根。一般而言,Markstein的方法需要初步结果,该结果在最终舍入序列之前精确到1 ulp以内。我没有足够的数学手段将他的技术扩展到立方体根的圆形,但在我看来,原则上这应该是可能的,这是一个有点类似于倒数平方根的挑战。

逐位算法很容易通过正确的舍入来计算根。由于IEEE-754舍入模式的连接情况不能发生,因此只需要进行计算,直到产生所有尾数位加上一个舍入位。基于二项式定理的平方根的逐位算法在非恢复和恢复变体中都是众所周知的,并且已经成为硬件实现的基础。通过二项式定理的相同方法适用于立方根,并且有一篇鲜为人知的论文,其中列出了非恢复实现的细节:

小时。 Peng,“提取平方根和立方根的算法”,Proceedings第5届IEEE国际计算机算术研讨会,第121-126页,1981年。

最好的我可以通过实验来判断这对于从整数中提取立方根来说非常有效。由于每次迭代只产生一个结果位,因此速度并不快。对于浮点运算中的应用,它具有使用几个簿记变量的缺点,这些变量需要大约两倍于最终结果的位数。这意味着需要使用128位整数运算来实现双精度立方根。

下面的我的C99代码基于Halley's rational method for the cube root,它具有立方收敛,这意味着初始近似不必非常精确,因为每次迭代中有效数字的数量为三倍。可以以各种方式安排计算。通常,将迭代方案安排为

在数值上是有利的

new_guess:= old_guess + correction

因为对于足够接近的初始猜测,correction明显小于old_guess。这导致了立方根的以下迭代方案:

x:= x - x *(x 3 - a)/(2 * x 3 + y)

此特定安排也列在Kahan's notes on cube root中。它的另一个优点是可以自然地使用FMA (fused-multiply add)。一个缺点是2 * x 3 的计算可能导致溢出,因此至少部分双精度输入域需要参数减少方案。在我的代码中,我只是简单地将参数简化应用于所有非异常输入,这是基于对IEEE-754双精度操作数的指数的直接操作。

区间[0.125,1]用作主要近似区间。使用多项式minimax近似,返回[0.5,1]中的初始猜测。窄范围有助于将单精度算术用于计算的低精度部分。

我无法证明我的实现的错误界限,但是,针对参考实现测试超过2亿个随机测试向量(精确到大约200位)检测到没有超过半个ulp的错误,这表明最大误差必须非常接近0.5 ulp。

double my_cbrt (double a)
{
    double b, u, v, r;
    float bb, uu, vv;
    int e, f, s;

    if ((a == 0.0) || isinf(a) || isnan(a)) {
        /* handle special cases */
        r = a + a;
    } else {
        /* strip off sign-bit */
        b = fabs (a);
        /* compute exponent adjustments */
        b = frexp (b, &e);
        s = e - 3*342;
        f = s / 3;
        s = s - 3 * f;
        f = f + 342;
        /* map argument into the primary approximation interval [0.125,1) */
        b = ldexp (b, s);
        bb = (float)b;
        /* approximate cube root in [0.125,1) with relative error 5.22e-3 */
        uu =                0x1.2f32c0p-1f;
        uu = fmaf (uu, bb, -0x1.62cc2ap+0f);
        uu = fmaf (uu, bb,  0x1.7546e0p+0f);
        uu = fmaf (uu, bb,  0x1.5d0590p-2f);
        /* refine cube root using two Halley iterations w/ cubic convergence */
        vv = uu * uu;
        uu = fmaf (fmaf (vv, uu, -bb) / fmaf (vv, 2.0f*uu, bb), -uu, uu);
        u = (double)uu;
        v = u * u; // this product is exact
        r = fma (fma (v, u, -b) / fma (v, 2.0*u, b), -u, u);
        /* map back from primary approximation interval by jamming exponent */
        r = ldexp (r, f);
        /* restore sign bit */
        r = copysign (r, a);
    }
    return r;
}

答案 2 :(得分:2)

我最近编写了一个正确舍入的立方根,在 https://github.com/mockingbirdnest/Principia/blob/master/numerics/cbrt.cpp 中实现并记录在 https://github.com/mockingbirdnest/Principia/blob/master/documentation/cbrt.pdf 中。 该实现采用 C++ 并使用英特尔内在函数,但将其调整为自己选择的语言和库应该很简单。

以下实际计算的简要总结;此处的参考文献使用了该文档中的书目代码,因为我显然太不名誉了,无法在此时将许多内容联系起来。对“第 I+ 部分”或“附录 [A-Z]”的引用为 cbrt.pdf。

正确的四舍五入。

这可以通过使用几乎正确的忠实方法来完成,如果结果太接近平局,则使用一轮经典的逐位算法(类似于长除法的算法) ) 得到第 54 位;由于该算法计算结果的位向 0 舍入,并且由于立方根没有中途情况,因此该位足以确定舍入到最接近的位置。

有可能使接近正确的方法比大多数现有的立方根实现更快,并且足够正确,平均成本基本上不受校正步骤的影响;见附录 D 和 F。

关于没有FMA的忠实方法的评论。

“几乎正确的忠实方法”部分可能更有趣;没有 FMA,基本轮廓与 Kahan 的 [KB01] 中的相同:

  1. 对给定浮点数表示的整数运算;
  2. 求根精度为三分之一;
  3. 四舍五入到精度的三分之一,以获得精确的立方体;
  4. 使用那个精确立方体的高阶方法的一个步骤。

在步骤 2 中使用一种巧妙的寻根方法,可以在保持或提高性能的同时降低误绕率。 这种情况我挖了个不合理的方法,

<块引用>

? ↦ ½? + √(¼?² + ?/(3?)) 近似于 ∛(?³+?),

出自 Thomas Fantet de Lagny 的 17 世纪著作,[Fan92];也许令人惊讶的是,因为它同时具有除法和平方根,所以它在适当重写时的性能(以便尽早安排除法,并避免平方根和除法之间的串行依赖性,参见附录 D)类似于更广为人知的有理方法(现在通常被称为哈雷,但哈雷考虑了这两种方法,当应用于立方根时,这两种方法都是由于 Lagny 引起的;参见第一部分的讨论)。这是因为有理法有一个较高的除数,所以它的除法不能提前安排。 非理性方法的误差是理性方法的一半。

在第 2 步结束时优化系数以最小化误差,其灵感来自于 Stephen Canon 的推文(两个推特线程,[Can18a] 和 [Can18b])在误差上又获得了两位那种不合理的方法,不花钱。

使用第 4 步中的 5 阶合理方法,该方法实现了每百万 4.564(68) 次的误绕率,这很容易在基本上没有平均成本的情况下纠正(缓慢的“潜在误绕”路径中的通过率为 2.6480(52)⋅10−4,慢路径的延迟小于快路径的 10 倍)。

关于 FMA 忠实方法的评论。

关键思想在njuffa对这个问题的回答中:步骤 4 可以只用一个精确的正方形来执行,不需要精确的立方体,所以步骤 2 应该瞄准,步骤 3 应该四舍五入到,精度的一半而不是三分之一。

与 njuffa 不同,我始终使用 double。在第 2 步中,我选择了 5 阶无理方法(通过推广 Lagny 方法获得;参见第一部分和附录 B),在第 4 步中,我选择了 4 阶而不是 3 阶有理方法。

由此产生的方法具有每十亿次 6.10(25) 的误绕率,以及 3.05(18)⋅10−7 在“潜在误绕”路径中的通过率。