TL; DR
- 使用以下函数代替当前接受的解决方案,以免在某些极限情况下产生某些不良结果,同时可能会提高效率。
- 了解数字的预期不精确度,并在比较函数中相应地输入它们。
bool nearly_equal(
float a, float b,
float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
{
assert(std::numeric_limits<float>::epsilon() <= epsilon);
assert(epsilon < 1.f);
if (a == b) return true;
auto diff = std::abs(a-b);
auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
return diff < std::max(relth, epsilon * norm);
}
图形,好吗?
比较浮点数时,有两个“模式”。
第一个是在相对模式,其中的区别x和y相对认为它们的振幅|x| + |y|。以2D绘制时,它具有以下轮廓,其中绿色表示x和相等y。(epsilon出于说明目的,我选择了0.5)。

相对模式是用于“正常”或“足够大”浮点值的模式。(稍后会详细介绍)。
第二种是绝对模式,当我们简单地将它们的差异与固定数字进行比较时。它给出以下轮廓(再次以epsilon0.5和relth1表示)。

这种绝对的比较模式用于“微小”的浮点值。
现在的问题是,我们如何将这两种响应模式缝合在一起。
在Michael Borgwardt的答案中,切换基于的值diff,该值应低于relth(Float.MIN_NORMAL在他的答案中)。下图以阴影线显示了此切换区域。

因为该relth * epsilon值较小relth,所以绿色色块不会粘在一起,这反过来又给解决方案带来了不好的性质:我们可以找到这样的三元组,即x < y_1 < y_2,而x == y2但x != y1。

请看以下引人注目的示例:
x = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
我们拥有x < y1 < y2,实际上y2 - x比它大2000倍以上y1 - x。但是使用当前的解决方案
nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
相反,在以上提出的解决方案中,切换区域基于的值|x| + |y|,该值由下面的阴影正方形表示。它可以确保两个区域正常连接。

另外,上面的代码没有分支,这可能会更有效率。考虑到操作,例如max和abs,其中先验需要支化,通常具有专用组装说明。出于这个原因,我认为这种方法优于另一种解决方案,该解决方案是nearlyEqual通过将开关从更改为来修复Michael的diff < relth方法diff < eps * relth,这将产生基本相同的响应模式。
在相对和绝对比较之间切换?
这些模式之间的切换是围绕进行的relth,这FLT_MIN与接受的答案相同。这种选择意味着的表示形式float32会限制我们的浮点数的精度。
这并不总是有意义。例如,如果您比较的数字是减法的结果,则可能在范围内FLT_EPSILON更有意义。如果它们是相减数的平方根,则数字不精确度可能更高。
当您考虑将浮点数与进行比较时,这很明显0。在这里,任何相对比较都会失败,因为|x - 0| / (|x| + 0) = 1。因此,当x您的计算不精确时,比较需要切换到绝对模式-很少会低到FLT_MIN。
这就是引入relth以上参数的原因。
而且,通过不relth与乘epsilon,该参数的解释很简单,并且与我们期望的这些数字的数值精度相对应。
数学隆隆声
(保留在这里主要是出于我的荣幸)
更一般而言,我假设行为良好的浮点比较运算符=~应具有一些基本属性。
以下内容非常明显:
- 自我平等:
a =~ a
- 对称性:
a =~ b暗示b =~ a
- 反对派的不变性:
a =~ b暗示-a =~ -b
(我们没有a =~ b并且b =~ c暗示a =~ c,=~不是等价关系)。
我将添加以下特定于浮点比较的属性
- 如果
a < b < c,则a =~ c表示a =~ b(更接近的值也应相等)
- 如果
a, b, m >= 0则a =~ b暗示a + m =~ b + m(具有相同差异的较大值也应相等)
- 如果
0 <= λ < 1然后a =~ b暗示λa =~ λb(可能不那么明显)。
这些属性已经对可能的近等函数给出了强大的约束。上面建议的功能可以验证它们。可能缺少一个或几个其他明显的属性。
当一个人认为是由和参数化=~的一族平等关系时,也可以添加=~[Ɛ,t]Ɛrelth
- 如果
Ɛ1 < Ɛ2然后a =~[Ɛ1,t] b暗示a =~[Ɛ2,t] b(给定公差的相等意味着更高公差的相等)
- 如果
t1 < t2然后a =~[Ɛ,t1] b暗示a =~[Ɛ,t2] b(给定不精确度的相等意味着较高不精确度的相等)
提出的解决方案也对此进行了验证。