14

通过类似的东西比较两个浮点数a_float == b_float正在寻找麻烦,因为由于舍入错误a_float / 3.0 * 3.0可能不等于。a_float

一个人通常做的是类似的事情fabs(a_float - b_float) < tol

一个如何计算tol

理想情况下,公差应该刚好大于一两个最低有效数字的值。因此,如果使用单精度浮点数tol = 10E-6应该是正确的。a_float但是,对于可能非常小或可能非常大的一般情况,这并不适用。

tol对于所有一般情况,如何正确计算?我特别对 C 或 C++ 案例感兴趣。

4

7 回答 7

19

这篇博文包含一个示例,相当简单的实现,以及它背后的详细理论 http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 它也是一个系列,因此您可以随时阅读更多内容。简而言之:对大多数数字使用 ULP,对接近零的数字使用 epsilon,但仍有一些注意事项。如果您想确定您的浮点数学,我建议您阅读整个系列。

于 2013-07-01T12:52:32.617 回答
10

据我所知,一个没有。

没有一般的“正确答案”,因为它可能取决于应用程序对精度的要求。

例如,在屏幕像素中工作的 2D 物理模拟可能会决定 1/4 像素就足够了,而用于设计核电站内部结构的 3D CAD 系统可能不会。

我看不到从外部以编程方式决定这一点的方法。

于 2013-07-01T12:29:52.743 回答
5

C 头文件<float.h>为您提供常量FLT_EPSILONDBL_EPSILON,这是 1.0 与浮点/双精度可以表示的大于 1.0 的最小数字之间的差。您可以根据您的数字大小和您希望容忍的舍入误差对其进行缩放:

#include <float.h>
#ifndef DBL_TRUE_MIN
/* DBL_TRUE_MIN is a common non-standard extension for the minimum denorm value
 * DBL_MIN is the minimum non-denorm value -- use that if TRUE_MIN is not defined */
#define DBL_TRUE_MIN DBL_MIN
#endif

/* return the difference between |x| and the next larger representable double */
double dbl_epsilon(double x) {
    int exp;
    if (frexp(x, &exp) == 0.0)
        return DBL_TRUE_MIN;
    return ldexp(DBL_EPSILON, exp-1);
}
于 2013-07-01T16:46:26.250 回答
4

欢迎来到陷阱、圈套和漏洞的世界。如其他地方所述,不存在用于浮点相等和容差的通用解决方案。鉴于此,程序员可以在特定情况下使用一些工具和公理。

fabs(a_float - b_float) < tol有提到的缺点 OP:“对于 a_float 可能非常小或可能非常大的一般情况,效果不佳。” fabs(a_float - ref_float) <= fabs(ref_float * tol)更好地应对变体范围。

OP 的“单精度浮点数使用 tol = 10E-6”对于 C 和 C++ 来说有点令人担忧,因此很容易float将算术提升到double,然后它的“容差” double,而不是float,发挥作用。考虑到float f = 1.0; printf("%.20f\n", f/7.0); 这么多新程序员没有意识到7.0导致了double精度计算。建议使用double您的代码,除非大量数据需要float较小的大小。

C99 提供nextafter()了有助于衡量“公差”的有用信息。使用它,可以确定下一个可表示的数字。这将有助于 OP“......存储类型的有效数字的全部数量减去一......以允许舍入错误。” if ((nextafter(x, -INF) <= y && (y <= nextafter(x, +INF))) ...

使用的那种tol“宽容”往往是问题的症结所在。大多数情况下(恕我直言)相对公差很重要。例如“x 和 y 是否在 0.0001% 之内”?有时需要绝对公差。例如“x 和 y 是否在 0.0001 范围内”?

公差的通常是有争议的,因为最佳值通常取决于情况。0.01 以内的比较可能适用于美元的财务申请,但不适用于日元。(提示:一定要使用易于更新的编码风格。)

于 2013-07-01T16:19:05.960 回答
1

舍入误差因用于运算的值而异。

您可以使用 epsilon 系数代替固定容差,例如:

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;
}
于 2016-02-07T12:03:11.083 回答
0

尽管公差的值取决于具体情况,但如果您正在寻找精度比较,您可以将机器 epsilon 值作为公差,numeric_limits::epsilon()(库限制)。该函数返回 1 与该数据类型可表示的大于 1 的最小值之间的差。 http://msdn.microsoft.com/en-us/library/6x7575x3.aspx

如果您比较浮点数或双精度数,则 epsilon 的值会有所不同。例如,在我的计算机中,如果比较浮点数,则 epsilon 的值为 1.1920929e-007,如果比较双倍,则 epsilon 的值为 2.2204460492503131e-016。

对于 x 和 y 之间的相对比较,将 epsilon 乘以 x 和 y 的最大绝对值。

上面的结果可以乘以 ulps(最后一个单位),这样您就可以精确地玩了。

#include <iostream>
#include <cmath>
#include <limits>

template<class T> bool are_almost_equal(T x, T y, int ulp)
{
    return std::abs(x-y) <= std::numeric_limits<T>::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp
}
于 2014-02-06T13:04:03.697 回答
-3

当我需要比较浮点数时,我使用这样的代码

bool same( double a, double b, double error ) {
    double x;
    if( a == 0 ) {
        x = b;
    } else if( b == 0 ) {
        x = a;
    } else {
        x = (a-b) / a;
    }
    return fabs(x) < error;
}
于 2013-07-01T13:52:58.603 回答