63

每个人都知道您不应该直接比较浮点数,而是使用容差:

float a,b;
float epsilon = 1e-6f;
bool equal = (fabs(a-b) < epsilon);

我想知道这是否同样适用于在除法中使用它之前将值与零进行比较。

float a, b;
if (a != 0.0f) b = 1/a; // oops?

在这种情况下,我是否还需要与 epsilon 进行比较?

4

5 回答 5

69

浮点除以零不是错误。它在支持浮点异常的实现上引发浮点异常(除非您正在积极检查它们,否则这是一个无操作),并具有明确定义的结果:正无穷或负无穷(如果分子非零),或NAN(如果分子为零)。

当分母不为零但非常接近零(例如次正规)时,也可能得到无穷大(和溢出异常),但这也不是错误。这就是浮点的工作原理。

编辑:请注意,正如 Eric 在评论中指出的那样,此答案假定附件 F 的要求,附件 F 是 C 标准的可选部分,详细说明了浮点行为并将其与 IEEE 浮点标准保持一致。在没有 IEEE 算法的情况下,C 没有定义浮点除以零(实际上,所有浮点运算的结果都是实现定义的,可能被定义为完全废话,仍然符合 C 标准),所以如果您正在处理一个不支持 IEEE 浮点的古怪 C 实现,您必须查阅用于回答此问题的实现的文档。

于 2012-08-24T18:17:45.703 回答
26

是的,在某些情况下,除以小数会导致与除以零相同的效果,包括陷阱。

一些 C 实现(和一些其他计算环境)可能会以刷新下溢模式执行,尤其是在使用高性能选项时。在这种模式下,除以次正规可以导致与除以零相同的结果。当使用向量 (SIMD) 指令时,刷新下溢模式并不少见。

次正规数是浮点格式中具有最小指数的那些,它们非常小以至于有效数的隐式位是 0 而不是 1。对于 IEEE 754,单精度,这是幅度小于 2 的非零数-126对于双精度,它是幅度小于 2 -1022的非零数字。

正确处理次正规数(根据 IEEE 754)在某些处理器中需要额外的计算时间。为了在不需要时避免这种延迟,处理器可能具有将次正规操作数转换为零的模式。然后将一个数除以次正规操作数将产生与除以零相同的结果,即使通常的结果是有限的。

如其他答案所述,在采用 C 标准附件 F 的 C 实现中,除以零不是错误。并非所有实现都可以。在不这样做的实现中,如果没有关于您的环境的额外规范,您无法确定是否启用了浮点陷阱,尤其是除零异常的陷阱。

根据您的情况,您可能还必须防止应用程序中的其他代码更改浮点环境。

于 2012-08-24T19:04:49.350 回答
9

要回答您帖子标题中的问题,除以非常小的数字不会导致除以零,但可能会导致结果变为无穷大:

double x = 1E-300;
cout << x << endl;
double y = 1E300;
cout << y << endl;
double z = y / x;
cout << z << endl;
cout << (z == std::numeric_limits<double>::infinity()) << endl;

会产生以下输出:

1e-300
1e+300
inf
1
于 2012-08-24T18:19:03.873 回答
8

只有除以 0.f 才会引发除以零异常。

但是,除以一个非常小的数字会产生溢出异常——结果太大以至于不能再用浮点数表示。除法将返回无穷大。

无穷大的浮点表示可用于计算,因此如果您的实现的其余部分可以处理它,则可能不需要检查它。

于 2012-08-24T18:20:30.257 回答
3

在这种情况下,我是否还需要与 epsilon 进行比较?

您永远不会收到除以零的错误,正如IEEE float0.0f中所准确表示的那样。

话虽如此,您可能仍希望使用一些容差 - 尽管这完全取决于您的应用程序。如果“零”值是其他数学运算的结果,则可能会得到一个非常小的非零数,这可能会导致除法后出现意外结果。如果您想将“接近零”的数字视为零,则可以使用容差。但是,这完全取决于您的应用程序和目标。

如果您的编译器使用IEEE 754 标准进行异常处理,那么除以零,以及除以一个小到足以导致溢出的值,都将导致 +/- infiniti 的值。这可能意味着您可能希望包含对非常小的数字的检查(这会导致您的平台溢出)。例如,在Windows上,float两者double都符合规范,这可能会导致一个非常小的除数创建 +/- infiniti,就像零值一样。

如果您的编译器/平台不遵循 IEEE 754 浮点标准,那么我相信结果是特定于平台的。

于 2012-08-24T18:08:41.780 回答