每个人都知道您不应该直接比较浮点数,而是使用容差:
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 进行比较?
每个人都知道您不应该直接比较浮点数,而是使用容差:
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 进行比较?
您永远不会收到除以零的错误,正如IEEE float0.0f
中所准确表示的那样。
话虽如此,您可能仍希望使用一些容差 - 尽管这完全取决于您的应用程序。如果“零”值是其他数学运算的结果,则可能会得到一个非常小的非零数,这可能会导致除法后出现意外结果。如果您想将“接近零”的数字视为零,则可以使用容差。但是,这完全取决于您的应用程序和目标。
如果您的编译器使用IEEE 754 标准进行异常处理,那么除以零,以及除以一个小到足以导致溢出的值,都将导致 +/- infiniti 的值。这可能意味着您可能希望包含对非常小的数字的检查(这会导致您的平台溢出)。例如,在Windows上,float
两者double
都符合规范,这可能会导致一个非常小的除数创建 +/- infiniti,就像零值一样。
如果您的编译器/平台不遵循 IEEE 754 浮点标准,那么我相信结果是特定于平台的。