【问题标题】:Dealing with Floating Point exceptions处理浮点异常
【发布时间】:2011-01-14 05:22:32
【问题描述】:

我不确定如何在 C 或 C++ 中处理浮点异常。从 wiki 中,有以下类型的浮点异常:

IEEE 754 specifies five arithmetic errors that are to be recorded in "sticky bits" (by default; note that trapping and other alternatives are optional and, if provided, non-default).  

* inexact, set if the rounded (and returned) value is different from the mathematically exact result of the operation.  
* underflow, set if the rounded value is tiny (as specified in IEEE 754) and inexact (or maybe limited to if it has denormalisation loss, as per the 1984 version of IEEE 754), returning a subnormal value (including the zeroes).  
* overflow, set if the absolute value of the rounded value is too large to be represented (an infinity or maximal finite value is returned, depending on which rounding is used).  
* divide-by-zero, set if the result is infinite given finite operands (returning an infinity, either +∞ or −∞).  
* invalid, set if a real-valued result cannot be returned (like for sqrt(−1), or 0/0), returning a quiet NaN.

是不是当出现上述任何一种异常时,程序都会异常退出?或者程序会在不提及任何内容的情况下继续此错误,从而使错误难以调试?

像 gcc 这样的编译器是否能够对一些明显的情况发出警告?

在编写程序时我可以做些什么来通知错误发生的位置以及错误发生时的类型,以便我可以在我的代码中轻松定位错误?请给出 C 和 C++ 两种情况的解决方案。

感谢和问候!

【问题讨论】:

  • 答案可能特定于操作系统。你有一个想法吗?
  • linux和windows都有,虽然我现在用的比较多。
  • 我看不出 ISO/IEEE 定义的浮点语义将如何依赖于操作系统。

标签: c++ c exception floating-point signals


【解决方案1】:

在装有 Visual C++ 的 Windows 上,您可以使用 _control87() etc. 控制哪些浮点异常不被屏蔽。未屏蔽的浮点异常会生成结构化异常,可以使用__try/__except(以及其他一些机制)来处理。这完全取决于平台。

如果您隐藏浮点异常,另一种检测这些条件的依赖于平台的方法是使用_clear87() etc. 清除浮点状态,执行计算,然后使用_status87() etc. 查询浮点状态。

这是否比 DigitalRoss 建议的检查结果更好?在大多数情况下,它不是。如果您需要检测(或控制)舍入(这不太可能),那么可能吗?

在使用 Borland/CodeGear/Embarcadero C++ 的 Windows 上,默认情况下某些浮点异常是未屏蔽的,这在使用未使用未屏蔽浮点异常进行测试的第三方库时通常会导致问题。

【讨论】:

    【解决方案2】:

    在 Linux 上,您可以使用 GNU 扩展 feenableexcept(隐藏在该页面的底部)打开浮点异常的捕获 - 如果您这样做,那么当发生异常时您将收到信号 SIGFPE然后你可以在你的调试器中捕获。请注意,有时信号会在实际导致问题的指令之后被抛出,从而在调试器中提供误导性的行信息!

    【讨论】:

    • 谢谢,迈克!如果不调用feenableexcept,SIGFPE 就不可能成为陷阱吗?如果仅使用 C 标准库通过 signal() 为 SIGFPE 指定处理程序,而不在 GNU 扩展中调用feenableexcept,是否会使我的程序接收 SIGFPE?
    【解决方案3】:

    C99 引入了处理浮点异常的函数。在浮点运算之前,您可以使用feclearexcept() 清除任何未完成的异常。操作完成后,您可以使用fetestexcept() 测试设置了哪些异常标志。

    【讨论】:

      【解决方案4】:

      有很多选项,但 754 引入的一般和默认理念是不是陷阱,而是产生特殊结果,例如可能或可能的无穷大不会出现在重要结果中。

      因此,测试单个操作状态的函数不像测试结果表示的函数那样经常使用。

      例如,请参阅...

      LIST OF FUNCTIONS
      
       Each of the functions that use floating-point values are provided in sin-
       gle, double, and extended precision; the double precision prototypes are
       listed here.  The man pages for the individual functions provide more
       details on their use, special cases, and prototypes for their single and
       extended precision versions.
      
       int fpclassify(double)
       int isfinite(double)
       int isinf(double)
       int isnan(double)
       int isnormal(double)
       int signbit(double)
      

      更新: 对于那些真正认为 FPU ops 在默认情况下生成 SIGFPE 的人,我鼓励你尝试这个程序。您可以轻松生成下溢、上溢和被零除。您不会生成的(除非您在最后一个幸存的 VAX 或非 754 RISC 上运行它)是 SIGFPE:

      #include <stdio.h>
      #include <stdlib.h>
      int main(int ac, char **av) { return printf("%f\n", atof(av[1]) / atof(av[2])); }
      

      【讨论】:

      • 我应该补充一点,你并不关心许多粘性位。下溢很少见,几乎与零相同。不精确太常见了。
      • 谢谢!测试每个表达式的结果似乎在代码中难以处理。即使对一些被认为很重要的结果进行了测试,没有测试的结果仍然很可能会抛出异常。我希望做的就是捕获异常以报告它发生在哪里以及它是什么类型。
      • 不会出现异常,操作后无需测试。 C99 确实引入了测试 FPU 粘性位的功能,但它们仍然不是普遍可用的,即使它们是你也不想浪费时间。
      【解决方案5】:

      不同的编译器以不同的方式处理这些错误。

      不精确几乎总是绝对值大于一的数字相除的结果(可能是通过超越函数)。绝对值 > 1.0 的数字的加减乘乘只会导致溢出。

      下溢并不经常发生,并且在正常计算中可能不会成为问题,除了泰勒级数等迭代函数。

      溢出是一个通常可以通过某种“无限”比较来检测的问题,不同的编译器会有所不同。

      除以零非常值得注意,因为如果您没有错误处理程序,您的程序将(应该)崩溃。检查股息和除数将有助于避免该问题。

      通常会在没有特殊错误处理程序的情况下捕获无效答案,并打印某种 DOMAIN 错误。

      [编辑]

      这可能会有所帮助:(Sun 的数值计算指南) http://docs.sun.com/source/806-3568/

      【讨论】:

        【解决方案6】:

        在 Linux 中,您可以通过捕获 SIGFPE 信号来捕获这些异常。如果您什么都不做,这些异常将终止您的程序。要设置处理程序,请使用信号函数,传递您希望捕获的信号,以及在信号触发时调用的函数。

        【讨论】:

        • 谢谢!是否可以知道并打印出处理信号的函数中发生异常的位置?
        • 这不是真的。通常,只有 754 之前的系统会为单个操作生成 SIGFPE。此外,诸如不精确和下溢之类的事情从来都不是例外。该程序可以生成 x/0、上溢和下溢。它不会生成 SIGFPE。 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; int main(int ac, char **av) { return printf("%f\n", atof(av[1]) / atof(av[2])); }
        猜你喜欢
        • 2021-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多