【问题标题】:IEEE-754 Floating-point Exceptions in CC 中的 IEEE-754 浮点异常
【发布时间】:2015-05-12 04:43:59
【问题描述】:

我正在用 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 中定义的函数的说明。

【问题讨论】:

  • 为什么不能手动检查数字的按位表示?
  • 阅读 C99 §7.6 “浮点环境”,尤其是 7.6.2 “浮点异常”。检测到异常后如何处理取决于您的应用程序。

标签: c floating-point c99 ieee-754


【解决方案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 都会很高兴地“优化掉”一个“死”浮点运算。 gcc bug 34678 关注这种行为,我想有一个类似的针对 clang 的错误。此警告也适用于在程序中使用舍入模式而不是舍入到最近的中断连线到偶数。

【讨论】:

  • 建议注明“标准”的名称。
  • @chux:如果你愿意的话,我很幸运能在这个答案中插入标识最新 C 和 C++ 标准的字母数字字符串。我认为这并不重要,因为标准在这里所说的内容与实现的内容完全不同。
【解决方案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

注意:Clang(至少)发出警告说它忽略了编译指示 STDS FENV_ACCESS 但它工作正常

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2012-01-06
      • 2014-10-18
      • 1970-01-01
      • 2013-06-12
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多