C++比较两个浮点数的值

5
我想知道以下两种方式在比较两个双精度浮点数时的区别:
方式一:
double a1 = ...;
double a2 = ....;
  1. fabs(a1-a2) < epsilon
  2. (fabs(a1-a2)/a2) < epsilon

有没有更好的方法?

谢谢


1
“prefering way” 很大程度上取决于周围的算法。两者都有用(还有更冗长的 fabs(a1 - a2) / min(a1, a2)fabs(a1 - a2) / max(a1, a2),尤其是当你不知道这些数字是否接近零时,max(fabs(a1 - a2), fabs(a1 - a2) / a1) 就更加重要了)。 - Alexandre C.
5个回答

13

这篇文章非常详细地回答了您的问题。您可能想跳到“Epsilon比较”一节。


添加了来自链接文章的引用,希望您不介意,如果您愿意,可以回滚。 - Binary Worrier
1
我担心那个引用并没有真正回答他的问题:我认为他意识到了精度问题,只是想知道为什么要除以a2。我考虑过最初引用一些内容,但我认为最好保留原文。我已经回滚并编辑以指示最相关的部分。 - Martin Stone

4

个人而言,我在比较两个 double 时使用 std::nextafter。这会使用一个值的最小 epsilon(或最小 epsilon 的一个 factor)。

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}

nearly_equal(..,..,epsilon)的结果不等于:fabs(right-left) < epsilon - Guillaume Paris
nearly_equal(a, b, factor) 函数使用 factor * epsilon(a) 的容差来检查两个数的相等性。fabs(a-b) < epsilon 函数使用给定的 epsilon 容差来检查两个数的相等性。这两个表达式都很好,可以根据我们想要测试的内容进行使用。有时候,我们想要使用固定的 epsilon 来检查(即使找到正确的 epsilon 很困难:epsilon (1e20) = 16384)。有时候,我们想要使用 epsilon 的因子来检查(即使找到正确的因子也很困难)。现在对于 fabs(a-b) < fabs(a) * epsilon,最好使用 nearly_equal(a, b, epsilon) 函数来提高准确性。 - Daniel Laügt

3

Epsilon的值根据双精度范围内的值而变化。如果您想使用自己的和固定的epsilon,我建议选择1的epsilon。

fabs(a1-a2) < epsilon

这并不完美。如果a1a2是由小数字进行计算得出的结果,那么epsilon将会很小。如果a1a2是由大数字进行计算得出的结果,那么epsilon将会很大。

fabs((a1-a2)/a2) < epsilon

如果您希望按比例缩放epsilon,那么这个方法就好一些了。但是,如果a2等于0,就会出现除以0的情况。

fabs(a1-a2) < fabs(a2)*epsilon

这个已经好一些了。然而,如果 a2 等于 0,这是不正确的。 fabs(a2)*epsilon 将等于 0 ,并且两个值 a1=0.000000001a2=0 的比较总是失败。

fabs(a1-a2) < max(fabs(a1), fabs(a2))*epsilon

这有一些改进。然而,这是不正确的,因为epsilon连续方面不成比例。使用IEEE编码,epsilon在以2为基数的离散方式上与一个值成比例。

fabs(a1-a2) < 2^((int)log2(max(fabs(a1), fabs(a2)))*epsilon

a1a2均不等于0时,我认为这看起来是正确的。

一个通用方法的实现可以是:

bool nearly_equal(double a1, double a2, double epsilon)
{
  if (a1 == 0 && a2 == 0)
    return true;

  return std::abs(a1 - a2) < epsilon * pow (2.0, static_cast<int> (std::log2(std::max(std::abs(a1), std::abs(a2)))));
}

2
这个问题已经得到了很好的解决,但还有一些要点需要说明:
fabs(a1-a2) < epsilon

比较a1a2绝对差异与容差epsilon的大小。如果您事先知道缩放比例(例如,如果a2实际上是一个常数),那么这可能是适当的,但如果您不知道a1a2有多大,则通常应避免使用它。

第二种选项几乎计算出了相对差异,但存在一个错误;它实际上应该是:

fabs((a1-a2)/a2) < epsilon

请注意,除法运算在绝对值符号内部;否则,这个条件对于负数的a2是无意义的。相对误差在大多数情况下更为准确,因为它更接近浮点舍入实际发生的方式,但有些情况下它并不适用,你需要使用绝对公差(通常是由于灾难性抵消)。有时你会看到以以下形式书写的相对误差界:

fabs(a1-a2) < fabs(a2)*epsilon

这通常会更有效,因为它避免了除法操作。


真的很喜欢最后一个表单,不太明白它在哪种情况下不起作用。 - Guillaume Paris
如果我的数字范围在10e-7到10e6之间,我应该使用哪个epsilon值? - Guillaume Paris
@Guillaume07:如果值分布在如此大的范围内,您肯定要使用相对误差比较之一(我列出的第二或第三个选项)。更微妙的问题是“我该如何选择适当的epsilon值?” - Stephen Canon
请注意,在进行相对差异epsilon比较时,您不应该盲目地除以a2(或a1)。如果这样做,那么当compare(a,b)产生与compare(b,a)不同的结果时,您会得到不对称性。那将是糟糕的。您需要始终除以具有最大或最小绝对值的数字。像这样:auto largest = max(fabs(a1), fabs(a2)); return fabs(a1 - a2) < fabs(largest)*epsilon; - Bruce Dawson

2

前者仅比较绝对值,而后者比较相对值。假设将epsilon设置为0.1:如果a1a2都较大,则这可能足够了。但是,如果两个值接近于零,第一种方法将认为大多数值相等。

这真的取决于您要处理的值的类型。如果使用稍微更具数学合理性的第二种情况,请务必考虑a2==0的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接