浮点平等和容差

时间:2013-07-01 12:28:00

标签: c++ c floating-point floating-accuracy

比较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 ++案例。

7 个答案:

答案 0 :(得分:16)

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

答案 1 :(得分:9)

据我所知,一个人没有。

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

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

我看不到从外面以编程方式决定这种情况的方法。

答案 2 :(得分:5)

C头文件<float.h>为您提供常量FLT_EPSILONDBL_EPSILON,这是1.0和float / double可以表示的大于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);
}

答案 3 :(得分:3)

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

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的“容差” {1}},而不是float,发挥作用。考虑float f = 1.0; printf("%.20f\n", f/7.0);许多新程序员没有意识到7.0导致double精度计算。建议您使用double代码,除非大量数据需要float更小的尺寸。

C99提供nextafter(),可用于帮助衡量“容忍度”。使用它,可以确定下一个可表示的数字。这将有助于OP“...存储类型的完整有效位数减去1 ...以允许舍入错误。” if ((nextafter(x, -INF) <= y && (y <= nextafter(x, +INF))) ...

使用的{em>种 tol或“容忍”通常是问题的症结所在。大多数情况下(恕我直言)相对容差很重要。即G。 “x和y是否在0.0001%之内”?有时需要绝对容差。例如“x和y是否在0.0001”之内?

容差的通常是有争议的,因为最佳值通常取决于情况。 0.01以内的比较可能适用于美元的财务申请,但不适用于日元。 (提示:请务必使用允许轻松更新的编码样式。)

答案 4 :(得分: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;
}

答案 5 :(得分: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)
{
    if( std::abs(x-y) <= std::numeric_limits<T>::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp ){
        return true; 
    }else{
        return false;
    }         
}

答案 6 :(得分:-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;
}