pow(浮动,浮动)算法

时间:2010-12-23 10:46:01

标签: algorithm math floating-point

我需要一个有效的算法来在两个浮点数之间执行math :: power函数, 你知道如何做到这一点,(我需要算法不使用函数本身)

2 个答案:

答案 0 :(得分:12)

通用算法倾向于将浮动功率计算为整数幂与剩余根的组合。整数幂相当简单,可以使用Newton - Raphson methodTaylor series来计算根。 IIRC numerical recipes in C有一些案文。还有其他(可能更好)的方法也可以这样做,但这将为实现一个令人惊讶的复杂问题提供一个合理的起点。另请注意,某些实现使用查找表和一些技巧来减少所需的计算。

答案 1 :(得分:7)

由于IEEE-754二进制浮点数是分数,因此计算 b 在技术上是一种代数运算。然而,实现powf(float a, float b)的常用方法是e b * log a ,即使用超越函数。

但是,有一些警告。 log a未定义a < 0,而powf()允许使用某些负a进行计算。正如我在this question的回答中所解释的那样,expf()形式的指数会出现误差放大。这要求我们使用高于单精度的log a来计算准确的powf()结果。有多种技术可以实现这一点,一种简单的方法是使用有限数量的双float计算,我在this question的答案中提供了参考。 double - float的本质是每个浮点操作数表示为一对float值,称为“head”和“tail”,它们满足关系| tail |。正确归一化时,≤½* ulp(| head |)。

下面的代码显示了此方法的示例性实现。它假定IEEE 754-2008操作FMA(融合乘法 - 加法)可用,它在C中作为标准数学函数fma()fmaf()公开。它不提供errno或浮点异常的处理,但它确实正确处理了ISO C标准列举的所有18个特殊情况。已启用非正规支持执行测试;在非IEEE-754清零到零(FTZ)环境中,代码可能正常工作,也可能无法正常工作。

取幂部分采用直接基于浮点数的半对数编码的简单参数约简,然后在初级近似区间上应用多项式minimax approximation。对数计算基于log(x) = 2 atanh ((x-1) / (x+1)),结合选择性使用双float计算,以实现3.65e-9的相对准确度。 b * log a的计算是作为双float运算执行的,通过观察e x 是它自己的导数,线性插值可以提高最终取幂的精度,因此,当| y |时,e x + y ≅e x + y·e x «| x |。

Double - float计算在溢出边界附近变得有点不确定,代码中有两个这样的实例。当输入到exp的头部仅仅导致结果溢出到无穷大时,尾部可能是负的,使得powf()的结果实际上是有限的。解决这个问题的一种方法是在这种情况下将“头部”的值减少一个ulp,另一种方法是通过在易于获得的圆到零模式下添加来计算头部,因为这样做确保头部和尾部的标志。另一个警告是,如果取幂确实溢出,我们就不能插入结果,因为这样做会产生NaN。

应该注意的是,这里使用的对数计算的准确性不足以确保忠实的powf()实现,但它提供了一个相当小的错误(我在广泛测试中发现的最大错误是小于2 ulps),它允许代码保持相当简单,以展示相关的设计原则。

#include <math.h> // for helper functions, e.g frexpf(), ldexpf(), isinff(), nextafterf()

/* Compute log(a) with extended precision, returned as a double-float value 
   loghi:loglo. Maximum relative error about 8.36545e-10.
*/
void my_logf_ext (float a, float *loghi, float *loglo)
{
    const float LOG2_HI =  0x1.62e430p-1f;  //  6.93147182e-1
    const float LOG2_LO = -0x1.05c610p-29f; // -1.90465421e-9

    float m, r, i, s, t, p, qhi, qlo;
    int e;

    /* Reduce argument to m in [181/256, 362/256] */
    m = frexpf (a, &e);
    if (m < 0.70703125f) { /* 181/256 */
        m = m + m;
        e = e - 1;
    }
    i = (float)e;

    /* Compute q = (m-1)/(m+1) as a double-float qhi:qlo */
    p = m + 1.0f;
    m = m - 1.0f;
    r = 1.0f / p;
    qhi = m * r;
    t = fmaf (qhi, -2.0f, m);
    s = fmaf (qhi, -m, t);
    qlo = r * s;

    /* Approximate atanh(q), q in [-75/437, 53/309] */ 
    s = qhi * qhi;
    r =             0x1.08c000p-3f;  // 0.1292724609
    r = fmaf (r, s, 0x1.22cde0p-3f); // 0.1419942379
    r = fmaf (r, s, 0x1.99a160p-3f); // 0.2000148296
    r = fmaf (r, s, 0x1.555550p-2f); // 0.3333332539
    t = fmaf (qhi, qlo + qlo, fmaf (qhi, qhi, -s)); // s:t = (qhi:qlo)**2
    p = s * qhi;
    t = fmaf (s, qlo, fmaf (t, qhi, fmaf (s, qhi, -p))); // p:t = (qhi:qlo)**3
    s = fmaf (r, p, fmaf (r, t, qlo));

    /* log(a) = 2 * atanh(q) + i * log(2) */
    t = fmaf ( LOG2_HI * 0.5f, i, qhi);
    p = fmaf (-LOG2_HI * 0.5f, i, t);
    s = (qhi - p) + s;                        
    s = fmaf ( LOG2_LO * 0.5f, i, s);
    r = t + t;
    *loghi = t = fmaf (2.0f, s, r);
    *loglo = fmaf (2.0f, s, r - t);
}

/* Compute exp(a). Maximum ulp error = 0.86565 */
float my_expf_unchecked (float a)
{
    float f, r;
    int i;

    // exp(a) = exp(i + f); i = rint (a / log(2))
    r = fmaf (0x1.715476p0f, a, 0x1.8p23f) - 0x1.8p23f; // 1.442695, 12582912.0
    f = fmaf (r, -0x1.62e400p-01f, a); // log_2_hi // -6.93145752e-1
    f = fmaf (r, -0x1.7f7d1cp-20f, f); // log_2_lo // -1.42860677e-6
    i = (int)r;
    // approximate r = exp(f) on interval [-log(2)/2,+log(2)/2]
    r =             0x1.694000p-10f; // 1.37805939e-3
    r = fmaf (r, f, 0x1.125edcp-7f); // 8.37312452e-3
    r = fmaf (r, f, 0x1.555b5ap-5f); // 4.16695364e-2
    r = fmaf (r, f, 0x1.555450p-3f); // 1.66664720e-1
    r = fmaf (r, f, 0x1.fffff6p-2f); // 4.99999851e-1
    r = fmaf (r, f, 0x1.000000p+0f); // 1.00000000e+0
    r = fmaf (r, f, 0x1.000000p+0f); // 1.00000000e+0
    // exp(a) = 2**i * exp(f);
    r = ldexpf (r, i);
    return r;
}

/* a**b = exp (b * log (a)), where a > 0, and log(a) is computed with extended 
   precision as a double-float.  The maximum ulp error found so far is 1.95083
   ulp for a = 0.698779583, b = 224.566528
*/
float my_powf_core (float a, float b)
{
    const float MAX_IEEE754_FLT = 0x1.fffffep127f;
    const float EXP_OVFL_BOUND = 0x1.62e430p+6f;
    const float EXP_OVFL_UNFL_F = 104.0f;
    const float MY_INF_F = 1.0f / 0.0f;
    float lhi, llo, thi, tlo, phi, plo, r;

    /* compute lhi:llo = log(a) */
    my_logf_ext (a, &lhi, &llo);
    /* compute phi:plo = b * log(a) */
    thi = lhi * b;
    if (fabsf (thi) > EXP_OVFL_UNFL_F) { // definitely overflow / underflow
        r = (thi < 0.0f) ? 0.0f : MY_INF_F;
    } else {
        tlo = fmaf (lhi, b, -thi);
        tlo = fmaf (llo, b, +tlo);
        /* normalize intermediate result thi:tlo, giving final result phi:plo */
#if FAST_FADD_RZ
        phi = __fadd_rz (thi, tlo);// avoid premature ovfl in exp() computation
#else // FAST_FADD_RZ
        phi = thi + tlo;
        if (phi == EXP_OVFL_BOUND){// avoid premature ovfl in exp() computation
            phi = nextafterf (phi, 0.0f);
        }
#endif // FAST_FADD_RZ
        plo = (thi - phi) + tlo;
        /* exp'(x) = exp(x); exp(x+y) = exp(x) + exp(x) * y, for |y| << |x| */
        r = my_expf_unchecked (phi);
        /* prevent generation of NaN during interpolation due to r = INF */
        if (fabsf (r) <= MAX_IEEE754_FLT) {
            r = fmaf (plo, r, r);
        }
    }
    return r;
}

float my_powf (float a, float b)
{
    const float MY_NAN_F = 0.0f / 0.0f;
    const float MY_INF_F = 1.0f / 0.0f;
    int expo_odd_int;
    float r;

    /* special case handling per ISO C specification */
    expo_odd_int = fmaf (-2.0f, floorf (0.5f * b), b) == 1.0f;
    if ((a == 1.0f) || (b == 0.0f)) {
        r = 1.0f;
    } else if (isnanf (a) || isnanf (b)) {
        r = a + b;  // convert SNaN to QNanN or trigger exception
    } else if (isinff (b)) {
        r = ((fabsf (a) < 1.0f) != (b < 0.0f)) ? 0.0f :  MY_INF_F;
        if (a == -1.0f) r = 1.0f;
    } else if (isinff (a)) {
        r = (b < 0.0f) ? 0.0f : MY_INF_F;
        if ((a < 0.0f) && expo_odd_int) r = -r;
    } else if (a == 0.0f) {
        r = (expo_odd_int) ? (a + a) : 0.0f;
        if (b < 0.0f) r = copysignf (MY_INF_F, r);
    } else if ((a < 0.0f) && (b != floorf (b))) {
        r = MY_NAN_F;
    } else {
        r = my_powf_core (fabsf (a), b);
        if ((a < 0.0f) && expo_odd_int) {
            r = -r;
        }
    }
    return r;
}