比较两个双精度浮点数或两个浮点数最有效的方法是什么?

简单地这样做是不正确的:

bool CompareDoubles1 (double A, double B)
{
   return A == B;
}

比如:

bool CompareDoubles2 (double A, double B) 
{
   diff = A - B;
   return (diff < EPSILON) && (-diff < EPSILON);
}

似乎是浪费加工。

有人知道更聪明的浮点比较器吗?


当前回答

/// testing whether two doubles are almost equal. We consider two doubles
/// equal if the difference is within the range [0, epsilon).
///
/// epsilon: a positive number (supposed to be small)
///
/// if either x or y is 0, then we are comparing the absolute difference to
/// epsilon.
/// if both x and y are non-zero, then we are comparing the relative difference
/// to epsilon.
bool almost_equal(double x, double y, double epsilon)
{
    double diff = x - y;
    if (x != 0 && y != 0){
        diff = diff/y; 
    }

    if (diff < epsilon && -1.0*diff < epsilon){
        return true;
    }
    return false;
}

我在我的小项目中使用了这个函数,它是有效的,但注意以下几点:

双精度误差可以为你制造惊喜。假设epsilon = 1.0e-6,那么根据上面的代码,1.0和1.000001不应该被认为是相等的,但在我的机器上,函数认为它们是相等的,这是因为1.000001不能精确地转换为二进制格式,它可能是1.0000009xxx。我用1.0和1.0000011测试了它,这次我得到了预期的结果。

其他回答

你必须为浮点数比较做这个处理,因为浮点数不能像整数类型那样完美地比较。下面是各种比较运算符的函数。

浮点数等于(==)

我也更喜欢减法技术,而不是依赖于fabs()或abs(),但我必须在从64位PC到ATMega328微控制器(Arduino)的各种架构上快速配置它,才能真正看到它是否会产生很大的性能差异。

所以,让我们忘记这些绝对值的东西,只做一些减法和比较!

从微软的例子修改如下:

/// @brief      See if two floating point numbers are approximately equal.
/// @param[in]  a        number 1
/// @param[in]  b        number 2
/// @param[in]  epsilon  A small value such that if the difference between the two numbers is
///                      smaller than this they can safely be considered to be equal.
/// @return     true if the two numbers are approximately equal, and false otherwise
bool is_float_eq(float a, float b, float epsilon) {
    return ((a - b) < epsilon) && ((b - a) < epsilon);
}
bool is_double_eq(double a, double b, double epsilon) {
    return ((a - b) < epsilon) && ((b - a) < epsilon);
}

使用示例:

constexpr float EPSILON = 0.0001; // 1e-4
is_float_eq(1.0001, 0.99998, EPSILON);

我不完全确定,但在我看来,对基于epsilon的方法的一些批评,正如这个高度好评的答案下面的评论所描述的那样,可以通过使用变量epsilon来解决,根据比较的浮点值缩放,像这样:

float a = 1.0001;
float b = 0.99998;
float epsilon = std::max(std::fabs(a), std::fabs(b)) * 1e-4;

is_float_eq(a, b, epsilon);

通过这种方式,epsilon值随浮点值伸缩,因此它的值不会小到不重要。

为了完整起见,让我们添加剩下的:

大于(>)小于(<):

/// @brief      See if floating point number `a` is > `b`
/// @param[in]  a        number 1
/// @param[in]  b        number 2
/// @param[in]  epsilon  a small value such that if `a` is > `b` by this amount, `a` is considered
///             to be definitively > `b`
/// @return     true if `a` is definitively > `b`, and false otherwise
bool is_float_gt(float a, float b, float epsilon) {
    return a > b + epsilon;
}
bool is_double_gt(double a, double b, double epsilon) {
    return a > b + epsilon;
}

/// @brief      See if floating point number `a` is < `b`
/// @param[in]  a        number 1
/// @param[in]  b        number 2
/// @param[in]  epsilon  a small value such that if `a` is < `b` by this amount, `a` is considered
///             to be definitively < `b`
/// @return     true if `a` is definitively < `b`, and false otherwise
bool is_float_lt(float a, float b, float epsilon) {
    return a < b - epsilon;
}
bool is_double_lt(double a, double b, double epsilon) {
    return a < b - epsilon;
}

大于或等于(>=),小于或等于(<=)

/// @brief      Returns true if `a` is definitively >= `b`, and false otherwise
bool is_float_ge(float a, float b, float epsilon) {
    return a > b - epsilon;
}
bool is_double_ge(double a, double b, double epsilon) {
    return a > b - epsilon;
}

/// @brief      Returns true if `a` is definitively <= `b`, and false otherwise
bool is_float_le(float a, float b, float epsilon) {
    return a < b + epsilon;
}
bool is_double_le(double a, double b, double epsilon) {
    return a < b + epsilon;
}

额外的改进:

A good default value for epsilon in C++ is std::numeric_limits<T>::epsilon(), which evaluates to either 0 or FLT_EPSILON, DBL_EPSILON, or LDBL_EPSILON. See here: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon. You can also see the float.h header for FLT_EPSILON, DBL_EPSILON, and LDBL_EPSILON. See https://en.cppreference.com/w/cpp/header/cfloat and https://www.cplusplus.com/reference/cfloat/ You could template the functions instead, to handle all floating point types: float, double, and long double, with type checks for these types via a static_assert() inside the template. Scaling the epsilon value is a good idea to ensure it works for really large and really small a and b values. This article recommends and explains it: http://realtimecollisiondetection.net/blog/?p=89. So, you should scale epsilon by a scaling value equal to max(1.0, abs(a), abs(b)), as that article explains. Otherwise, as a and/or b increase in magnitude, the epsilon would eventually become so small relative to those values that it becomes lost in the floating point error. So, we scale it to become larger in magnitude like they are. However, using 1.0 as the smallest allowed scaling factor for epsilon also ensures that for really small-magnitude a and b values, epsilon itself doesn't get scaled so small that it also becomes lost in the floating point error. So, we limit the minimum scaling factor to 1.0. If you want to "encapsulate" the above functions into a class, don't. Instead, wrap them up in a namespace if you like in order to namespace them. Ex: if you put all of the stand-alone functions into a namespace called float_comparison, then you could access the is_eq() function like this, for instance: float_comparison::is_eq(1.0, 1.5);. It might also be nice to add comparisons against zero, not just comparisons between two values. So, here is a better type of solution with the above improvements in place: namespace float_comparison { /// Scale the epsilon value to become large for large-magnitude a or b, /// but no smaller than 1.0, per the explanation above, to ensure that /// epsilon doesn't ever fall out in floating point error as a and/or b /// increase in magnitude. template<typename T> static constexpr T scale_epsilon(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept { static_assert(std::is_floating_point_v<T>, "Floating point comparisons " "require type float, double, or long double."); T scaling_factor; // Special case for when a or b is infinity if (std::isinf(a) || std::isinf(b)) { scaling_factor = 0; } else { scaling_factor = std::max({(T)1.0, std::abs(a), std::abs(b)}); } T epsilon_scaled = scaling_factor * std::abs(epsilon); return epsilon_scaled; } // Compare two values /// Equal: returns true if a is approximately == b, and false otherwise template<typename T> static constexpr bool is_eq(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept { static_assert(std::is_floating_point_v<T>, "Floating point comparisons " "require type float, double, or long double."); // test `a == b` first to see if both a and b are either infinity // or -infinity return a == b || std::abs(a - b) <= scale_epsilon(a, b, epsilon); } /* etc. etc.: is_eq() is_ne() is_lt() is_le() is_gt() is_ge() */ // Compare against zero /// Equal: returns true if a is approximately == 0, and false otherwise template<typename T> static constexpr bool is_eq_zero(T a, T epsilon = std::numeric_limits<T>::epsilon()) noexcept { static_assert(std::is_floating_point_v<T>, "Floating point comparisons " "require type float, double, or long double."); return is_eq(a, (T)0.0, epsilon); } /* etc. etc.: is_eq_zero() is_ne_zero() is_lt_zero() is_le_zero() is_gt_zero() is_ge_zero() */ } // namespace float_comparison

参见:

The macro forms of some of the functions above in my repo here: utilities.h. UPDATE 29 NOV 2020: it's a work-in-progress, and I'm going to make it a separate answer when ready, but I've produced a better, scaled-epsilon version of all of the functions in C in this file here: utilities.c. Take a look. ADDITIONAL READING I need to do now have done: Floating-point tolerances revisited, by Christer Ericson. VERY USEFUL ARTICLE! It talks about scaling epsilon in order to ensure it never falls out in floating point error, even for really large-magnitude a and/or b values!

有关更深入的方法,请参阅比较浮点数。以下是该链接的代码片段:

// Usable AlmostEqual function    
bool AlmostEqual2sComplement(float A, float B, int maxUlps)    
{    
    // Make sure maxUlps is non-negative and small enough that the    
    // default NAN won't compare as equal to anything.    
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);    
    int aInt = *(int*)&A;    
    // Make aInt lexicographically ordered as a twos-complement int    
    if (aInt < 0)    
        aInt = 0x80000000 - aInt;    
    // Make bInt lexicographically ordered as a twos-complement int    
    int bInt = *(int*)&B;    
    if (bInt < 0)    
        bInt = 0x80000000 - bInt;    
    int intDiff = abs(aInt - bInt);    
    if (intDiff <= maxUlps)    
        return true;    
    return false;    
}

我对任何涉及浮点减法的答案都非常谨慎(例如,fabs(a-b) < epsilon)。首先,浮点数在更大的量级上变得更稀疏,在足够大的量级上,当间隔大于时,您可能只需要做a == b。其次,减去两个非常接近的浮点数(因为您正在寻找接近相等的浮点数)正是您得到灾难性抵消的方式。

虽然不能移植,但我认为grom的答案在避免这些问题方面做得最好。

我的方法也许不正确,但很有用

将两个浮点数都转换为字符串,然后进行字符串比较

bool IsFlaotEqual(float a, float b, int decimal)
{
    TCHAR form[50] = _T("");
    _stprintf(form, _T("%%.%df"), decimal);


    TCHAR a1[30] = _T(""), a2[30] = _T("");
    _stprintf(a1, form, a);
    _stprintf(a2, form, b);

    if( _tcscmp(a1, a2) == 0 )
        return true;

    return false;

}

也可以做到操作人员超载

意识到这是一个老话题,但这篇文章是我发现的关于比较浮点数的最直接的文章之一,如果你想探索更多,它也有更详细的参考资料,它的主要站点涵盖了处理浮点数的完整范围的问题《浮点指南:比较》。

我们可以在浮点公差中找到一篇更实用的文章,并指出有绝对公差测试,在c++中归结为:

bool absoluteToleranceCompare(double x, double y)
{
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}

及相对耐量试验:

bool relativeToleranceCompare(double x, double y)
{
    double maxXY = std::max( std::fabs(x) , std::fabs(y) ) ;
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXY ;
}

文章指出,当x和y较大时,绝对检验失败;当x和y较小时,相对检验失败。假设绝对耐受性和相对耐受性是相同的,综合测试将是这样的:

bool combinedToleranceCompare(double x, double y)
{
    double maxXYOne = std::max( { 1.0, std::fabs(x) , std::fabs(y) } ) ;

    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXYOne ;
}