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

Posted

技术标签:

【中文标题】接近零的浮点值会导致被零除的错误吗?【英文标题】:Can a near-zero floating value cause a divide-by-zero error? 【发布时间】:2012-08-20 07:39:08 【问题描述】:

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

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

【问题讨论】:

根本不检查 0。让它崩溃吧! 每个人都知道你不应该使用公差来比较浮点数,而是以一种对你想要的有意义的方式。 @LuchianGrigore 它不会崩溃。 如果 b 不完全为 0,则不会出错。但如果 b 太小,结果可能没有意义,这取决于您的应用程序逻辑。 @LuchianGrigore 仅当您不知道在 0 的情况下该怎么做时。但我很确定在某些情况下特别对待 0 是有意义的。此外,并非所有环境都会使其崩溃,有些环境只会给您一个奇怪的值(我不确定它是 NaN、无穷大之一还是其​​他)。 如果适合您的情况,可以考虑使用Q numbers... 【参考方案1】:

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

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

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

【讨论】:

让我强调一下:浮点异常不是 C++ 异常。你不能在它周围放置一个 try-catch 块。除非你去寻找它,否则你不会看到它,这需要一些专业知识。正如@R.. 所说,除非您知道如何找到它,否则它是无操作的。除非你是专家,否则你不想这样做。 感谢皮特强调这一点。除非您使用fenv.h(大多数人甚至没有听说过,更不用说使用它了),否则浮点异常是无关紧要的/无操作的。 C 标准没有定义默认的浮点环境,除非实现采用附件 F。没有附件 F,陷阱可能默认启用。在编译时使用高性能选项可能会启用不符合 C 标准或不符合 IEEE 754 的模式。被零除不是错误的笼统声明是没有根据的。 没有附件F,对浮点的行为基本没有要求。 2.0+2.0==5.0 可以是真的。不假设附件F就谈C中的浮点是没有意义的。 您能否引用 C 标准中暗示 2.0+2.0!=5.0 的部分?我不知道有任何此类要求。【参考方案2】:

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

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

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

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

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

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

【讨论】:

完全挑剔的一点:IEEE-754 的 2008 年修订版将“非正常”更改为“次正常”。术语的这种变化不会改变上述任何一点。 requires additional computing time in some processors 事实上,它不在一些处理器中——它在大多数当前处理器中,包括x86和ARM处理器。跨度> 【参考方案3】:

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

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;

这个produces下面的输出:

1e-300
1e+300
inf
1

【讨论】:

是否可以测试一个值是否为无穷大? z!=z 对于无穷大是错误的。这只适用于 nans。 @neuviemeporte 是的 - 看看答案的更新。 @R.. 谢谢,我去检查了这个技巧,但它不起作用,所以我用更长的方法检查它。 @neuviemeporte 由于 math.h 中的 C99 定义了宏 int isfinite(float x),因此仅当 x 为有限时才返回非零结果。见 pubs.opengroup.org/onlinepubs/009604499/functions/isfinite.html【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

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

您永远不会收到除以零的错误,因为 0.0f 完全在 IEEE float 中表示。

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

如果您的编译器使用IEEE 754 standards for exception handling,那么除以零,以及除以一个小到足以导致溢出的值,都将导致值 +/- infiniti。这可能意味着您可能希望包含对非常小的数字的检查(这会导致您的平台溢出)。例如,在Windows、floatdouble 上都符合规范,这可能会导致非常小的除数创建 +/- 无穷大,就像零值一样。

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

【讨论】:

真的是这个问题吗? 0 被精确表示很好,但这是否意味着没有其他值可以具有与被零除相同的结果? @delnan 这不是很清楚 - 考虑到问题“接近零的浮点值会导致被零除错误吗?” - 是的,我认为这直接回答了它;)我确实试图提出其他观点,但在我的回答中...... 我也指的是问题的那一部分。毕竟,可以想象(对于未受过教育的我)一些非常接近零的值会导致与除以精确零相同的浮点异常。 -1 现在。 @delnan 通常,真正的“除以零”实际上只是除以零。我相信这可能是特定于平台的 - 例如,在 Windows 上, div/0 会引发 EXCEPTION_FLT_DIVIDE_BY_ZERO 我相信 div/(nr 0) 可以在 Windows 上引发 EXCEPTION_FLT_OVERFLOW - 请参阅:msdn.microsoft.com/en-us/library/windows/desktop/… @delnan 添加(查找后)IEEE 754 浮点规则 - 这可能是最接近可以使用的“标准”的东西。

以上是关于接近零的浮点值会导致被零除的错误吗?的主要内容,如果未能解决你的问题,请参考以下文章

除了被零除之外,还都有哪些情况会发生浮点异常?

Java中与&&不一致和被零除错误

我在python中得到“被零除”错误,但我不知道为什么

SET ANSI_WARNINGS { ON | OFF }

避免在 PostgreSQL 中被零除

Jvm(44),指令集----运算指令