首页 文章

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

提问于
浏览
4

我在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 回答

  • 1

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

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

    标准规定您需要 #pragma FENV_ACCESS ON 才能使用它 . 但是,我还没有使用一个关心该编译指示的编译器或一个知道浮点乘法在异常标志中反映出副作用的编译器 - gcc和clang都会愉快"dead"一个"dead"浮点运算 . gcc bug 34678关注这种行为,我想有一个类似的反对clang的错误 . 这个警告也适用于在程序中使用舍入模式而不是舍入到最接近的断开关系 .

  • 2

    如果您正在构建计算器接口并希望处理浮点异常,则应该:

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

    你没有任何帮助,所以你必须手工实施 . 示例:

    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,但它工作正常

  • 4

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

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

相关问题