C语言中的IEEE-754浮点异常

时间:2015-05-12 04:44:00

标签: c floating-point c99 ieee-754

我在C中编写一个浮点计算器接口,它允许在运行时访问math.h中定义的数学函数。该接口实现为一个行为类似于strtold()的函数。它基于ASCII,并且应该像ASCII一样可移植,但为了使其真实,我需要以尽可能便携的方式处理浮点。我很高兴限制对IEEE-754浮点的支持,但我不知道如何处理IEEE-754定义的异常(溢出,下溢等)。首先,我非常确定检查在所有舍入模式下工作的异常的唯一方法是检查状态标志本身;为了做到这一点,我将需要fenv.h(在C99的附录F中定义),所以我想知道如何在实践中可移植fenv.h。我也不完全理解fenv.h应该如何工作;在我看来,状态标志是集中的,但无论出于何种原因,我都认为每个浮点都有内置的标志。另外我知道C99说math.h中定义的函数可能溢出和下溢但我不明白我应该如何检查这些异常。总结一下,我正在寻找一个例子,说明如何使用fenv.h检查乘法引起的溢出,并解释如何正确地检查math.h中定义的函数。

3 个答案:

答案 0 :(得分:5)

理论上,以下函数将两个数相乘,如果发生溢出则返回true,如果不发生则返回false

bool mul(double &a, double b) {
  feclearexcept(FE_OVERFLOW);
  a *= b;
  return fetestexcept(FE_OVERFLOW) != 0;
}

标准声明您需要#pragma FENV_ACCESS ON才能使用此功能。但是,我还没有使用一个关心那个pragma的编译器或一个知道浮点乘法在异常标志中反映出副作用的编译器 - gcc和clang都会愉快地"优化掉&#34 ; a"死了"浮点运算。 gcc bug 34678关注这种行为,我想有一个与clang类似的错误。这个警告也适用于任何使用舍入模式,而不是在程序中使用舍入到最接近的断开关系。

答案 1 :(得分:3)

如果您正在构建计算器界面并想要处理浮点异常,您应该:

  • 在任何浮点运算之前重置标志
  • 操作后的测试标志

您将无法获得帮助,因此您必须手动执行 。示例:

double mul(double a, double b, int *status) {
    #pragma STDC FENV_ACCESS ON
    fexcept_t flags;
    int sv_status, f_status = -1;
    double resul;

    sv_status = fegetexceptflag(&flags, FE_ALL_EXCEPT) != 0; /* save flags */
    if (sv_status == 0) {
        f_status = feclearexcept(FE_ALL_EXCEPT);   /* clear all fp exception con
ditions */
    }
    resul = a * b;
    if (f_status == 0) {
        *status = fetestexcept(FE_ALL_EXCEPT); /* note conditions */
    }
    if (sv_status == 0) {
        fesetexceptflag(&flags, FE_ALL_EXCEPT); /* restore initial flags */
    }
    return resul;

}

演示:

int main ()
{
        double d2, d3, d4;
        int status;
        double d = 1e100;

        feraiseexcept(FE_OVERFLOW | FE_INEXACT);
        status = fetestexcept(FE_ALL_EXCEPT);
        printf("initial status : %x\n", status);
        d2 = mul(3., 4., &status);
        printf("resul 3 * 4 : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d, d, &status);
        printf("resul d * d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d*d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        d2 = mul(d2, d, &status);
        printf("resul d *d *d*d*d : %g - status %x (%x)\n", d2,
                status, fetestexcept(FE_ALL_EXCEPT));
        return 0;
}

给出:

initial status : 28
resul 3 * 4 : 12 - status 0 (28)
resul d * d : 1e+200 - status 20 (28)
resul d *d *d : 1e+300 - status 20 (28)
resul d *d *d*d : inf - status 28 (28)
resul d *d *d*d*d : inf - status 0 (28)

这意味着mul

  • 正确设置状态标志
  • 保持浮点异常标志不变

由于此代码仅使用C规范中定义的宏和函数,因此它应该适用于任何符合C99的编译器。

现在由您来实际处理该标志。

参考文献:ISO/IEC 9899:201x (ISO C11) Committee Draft

Nota:Clang(至少)发出警告说它忽略了pragma STDS FENV_ACCESS但它运行良好

答案 2 :(得分:2)

实际上,没有人检查浮点异常。任何关心(并且是少数!)的人都会检查返回的值:您可以轻松检查结果是NaN,+ / - 无穷大还是非规范化数字。您需要手动检查以检测零是乘以或除非零数的结果,但这很容易。

没有人检查异常的一个原因是,我发现让它与任何单个编译器一起正常工作是一项挑战,并且不可能让它与众多编译器一起工作。