接近零的浮动值会导致被零除的错误吗

Can a near-zero floating value cause a divide-by-zero error?

本文关键字:错误 接近      更新时间:2023-10-16

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

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进行比较?

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

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

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

是的,在某些情况下,用小数字除可以产生与用零除相同的效果,包括陷阱。

一些C实现(以及其他一些计算环境)可能以刷新下溢模式执行,尤其是在使用高性能选项的情况下。在这种模式下,除以一个次法线可以得到与除以零相同的结果。当使用矢量(SIMD)指令时,刷新下溢模式并不少见。

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

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

正如其他答案中所指出的,在采用C标准附录F的C实现中,除以零不是错误。并不是所有的实现都启用了。在没有启用的实现中,如果没有关于环境的其他规范,您就无法确定是否启用了浮点陷阱,特别是除以零异常的陷阱。

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

要回答帖子标题中的问题,除以一个很小的数字不会导致除以零,但可能会导致结果变成无穷大:

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

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

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

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

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

您永远不会收到被零除的错误,因为0.0f正是在IEEE浮点中表示的。

话虽如此,你可能仍然想使用一些宽容度——尽管这完全取决于你的应用程序。如果"零"值是其他数学运算的结果,则可能会得到一个非常小的非零数字,这可能会导致除法后出现意外的结果。如果您想将"接近零"的数字视为零,则公差是合适的。然而,这完全取决于您的应用程序和目标。

如果您的编译器使用IEEE 754标准进行异常处理,那么除以零,以及除以一个小到足以导致溢出的值,都会导致值为+/-infiniti。这可能意味着你可能想要包括对非常小的数字的检查(这会导致你的平台上溢出)。例如,在Windows上,floatdouble都符合规范,这可能会导致一个非常小的除数产生+/-infiniti,就像零值一样。

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