处理浮点异常

13

我不确定如何在C或C++中处理浮点异常。从维基百科得知,有以下几种类型的浮点异常:

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++给出解决方案。

谢谢和问候!


你有特定的操作系统想法吗?答案可能会与操作系统有关。 - John Knoeller
无论是Linux还是Windows,尽管我现在更经常使用Linux。 - Tim
1
我不明白ISO/IEEE定义的浮点语义如何与操作系统相关。 - Jeff Hammond
6个回答

12

有很多选择,但由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 操作在默认情况下会产生 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])); }

我应该补充一下,你并不需要关心许多粘性位。下溢很少发生,与零大致相同。不精确太常见了,也不需要关心。 - DigitalRoss
谢谢!测试每个表达式的结果在代码中似乎太过繁琐。即使对于一些被视为重要的结果进行测试,没有经过测试的结果仍然很可能抛出异常。我所希望做的就是捕获异常以报告发生异常的位置和类型。 - Tim
不会出现异常,操作后无需测试。C99确实引入了用于测试FPU粘滞位的函数,但它们仍然不是普遍可用的,即使它们是,你也不想浪费时间。 - DigitalRoss

6
在Linux上,您可以使用GNU扩展 feenableexcept(位于该页面底部)打开浮点异常捕获功能。如果这样做,当异常发生时,您将收到SIGFPE信号,然后您可以在调试器中捕获它。但要注意,有时候信号会在实际引起问题的浮点指令之后抛出,从而导致调试器中的行信息具有误导性!

谢谢,迈克!如果没有调用feenableexcept,是否不可能捕获SIGFPE?如果只使用C标准库通过signal()指定SIGFPE的处理程序而没有调用GNU扩展中的feenableexcept,那么我的程序会收到SIGFPE吗? - Tim

4
在使用Visual C++的Windows平台上,您可以使用_control87()控制哪些浮点异常未屏蔽。未屏蔽的浮点异常会生成结构化异常,可以使用__try/__except(和其他一些机制)处理。这完全取决于平台。
如果您保留浮点异常屏蔽,检测这些条件的另一种平台相关方法是使用_clear87()清除浮点状态,执行计算,然后使用_status87()查询浮点状态。
这些方法是否比DigitalRoss的建议更好?在大多数情况下,不是。如果您需要检测(或控制)舍入(这不太可能),那么可能会有所帮助。
在使用 Borland/CodeGear/Embarcadero C++ 的 Windows 系统上,默认情况下会取消屏蔽某些浮点数异常,这经常会在使用未测试过浮点数异常的第三方库时引起问题。

2
不同的编译器处理这些错误的方式不同。
不精确通常是绝对值大于1的数字相除(可能通过超越函数)。将绝对值> 1.0的数字相加、减去和相乘只能导致溢出。
下溢很少发生,除了Taylor系列等迭代函数之外,在正常计算中可能不会引起关注。
溢出是一个问题,通常可以通过某种“无限”比较来检测,不同的编译器有所不同。
除以零非常明显,因为如果没有错误处理程序,您的程序将(应该)崩溃。检查被除数和除数将有助于避免此问题。
无效答案通常会在打印某些域错误时被捕获,无需特殊的错误处理程序。
[编辑]
这可能有所帮助:(Sun的数值计算指南) http://docs.sun.com/source/806-3568/

1
C99引入了处理浮点异常的函数。在执行浮点操作之前,您可以使用feclearexcept()来清除所有未处理的异常。在操作完成后,您可以使用fetestexcept()来测试设置了哪些异常标志位。

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

谢谢!在处理信号的函数中,是否有可能知道并打印出异常发生的位置? - Tim
这不是真的。一般来说,只有在 pre-754 系统中才会为单个操作生成 SIGFPE。而且,像不精确和下溢这样的情况从来没有成为异常。该程序可以生成 x/0、溢出和下溢。它不会生成 SIGFPE。#include <stdio.h> #include <stdlib.h> int main(int ac, char **av) { return printf("%f\n", atof(av[1]) / atof(av[2])); } - DigitalRoss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接