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

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

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

比如:

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

似乎是浪费加工。

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


当前回答

我为java编写这篇文章,但是您可能会发现它很有用。它使用长变量而不是双变量,但会处理nan、亚法线等。

public static boolean equal(double a, double b) {
    final long fm = 0xFFFFFFFFFFFFFL;       // fraction mask
    final long sm = 0x8000000000000000L;    // sign mask
    final long cm = 0x8000000000000L;       // most significant decimal bit mask
    long c = Double.doubleToLongBits(a), d = Double.doubleToLongBits(b);        
    int ea = (int) (c >> 52 & 2047), eb = (int) (d >> 52 & 2047);
    if (ea == 2047 && (c & fm) != 0 || eb == 2047 && (d & fm) != 0) return false;   // NaN 
    if (c == d) return true;                            // identical - fast check
    if (ea == 0 && eb == 0) return true;                // ±0 or subnormals
    if ((c & sm) != (d & sm)) return false;             // different signs
    if (abs(ea - eb) > 1) return false;                 // b > 2*a or a > 2*b
    d <<= 12; c <<= 12;
    if (ea < eb) c = c >> 1 | sm;
    else if (ea > eb) d = d >> 1 | sm;
    c -= d;
    return c < 65536 && c > -65536;     // don't use abs(), because:
    // There is a posibility c=0x8000000000000000 which cannot be converted to positive
}
public static boolean zero(double a) { return (Double.doubleToLongBits(a) >> 52 & 2047) < 3; }

请记住,在一些浮点运算之后,number可能与我们期望的非常不同。没有代码可以解决这个问题。

其他回答

与epsilon值进行比较是大多数人所做的(甚至是在游戏编程中)。

你应该稍微改变你的实现:

bool AreSame(double a, double b)
{
    return fabs(a - b) < EPSILON;
}

编辑:克里斯特在最近的一篇博客文章中添加了一堆关于这个主题的很棒的信息。享受。

你写的代码有bug:

return (diff < EPSILON) && (-diff > EPSILON);

正确的代码应该是:

return (diff < EPSILON) && (diff > -EPSILON);

(…是的,这是不同的)

我想知道晶圆厂是否会让你在某些情况下失去懒惰的评价。我会说这取决于编译器。你可能想两种都试试。如果它们在平均水平上是相等的,则采用晶圆厂实现。

如果你有一些关于两个浮点数中哪一个比另一个更大的信息,你可以根据比较的顺序来更好地利用惰性求值。

最后,通过内联这个函数可能会得到更好的结果。不过不太可能有太大改善……

编辑:OJ,谢谢你纠正你的代码。我相应地删除了我的评论

我的课程是基于之前发布的答案。非常类似于谷歌的代码,但我使用了一个偏差,将所有NaN值推到0xFF000000以上。这样可以更快地检查NaN。

这段代码是为了演示概念,而不是通用的解决方案。谷歌的代码已经展示了如何计算所有平台特定的值,我不想复制所有这些。我对这段代码做了有限的测试。

typedef unsigned int   U32;
//  Float           Memory          Bias (unsigned)
//  -----           ------          ---------------
//   NaN            0xFFFFFFFF      0xFF800001
//   NaN            0xFF800001      0xFFFFFFFF
//  -Infinity       0xFF800000      0x00000000 ---
//  -3.40282e+038   0xFF7FFFFF      0x00000001    |
//  -1.40130e-045   0x80000001      0x7F7FFFFF    |
//  -0.0            0x80000000      0x7F800000    |--- Valid <= 0xFF000000.
//   0.0            0x00000000      0x7F800000    |    NaN > 0xFF000000
//   1.40130e-045   0x00000001      0x7F800001    |
//   3.40282e+038   0x7F7FFFFF      0xFEFFFFFF    |
//   Infinity       0x7F800000      0xFF000000 ---
//   NaN            0x7F800001      0xFF000001
//   NaN            0x7FFFFFFF      0xFF7FFFFF
//
//   Either value of NaN returns false.
//   -Infinity and +Infinity are not "close".
//   -0 and +0 are equal.
//
class CompareFloat{
public:
    union{
        float     m_f32;
        U32       m_u32;
    };
    static bool   CompareFloat::IsClose( float A, float B, U32 unitsDelta = 4 )
                  {
                      U32    a = CompareFloat::GetBiased( A );
                      U32    b = CompareFloat::GetBiased( B );

                      if ( (a > 0xFF000000) || (b > 0xFF000000) )
                      {
                          return( false );
                      }
                      return( (static_cast<U32>(abs( a - b ))) < unitsDelta );
                  }
    protected:
    static U32    CompareFloat::GetBiased( float f )
                  {
                      U32    r = ((CompareFloat*)&f)->m_u32;

                      if ( r & 0x80000000 )
                      {
                          return( ~r - 0x007FFFFF );
                      }
                      return( r + 0x7F800000 );
                  }
};

不幸的是,即使您的“浪费”代码也是不正确的。EPSILON是可以添加到1.0并更改其值的最小值。值1.0非常重要——更大的数字在添加到EPSILON时不会改变。现在,您可以将这个值缩放到您正在比较的数字,以判断它们是否不同。比较两个双精度对象的正确表达式是:

if (fabs(a - b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

这是最小值。一般来说,你会想要在计算中考虑噪声,并忽略一些最不重要的位,所以更现实的比较应该是这样的:

if (fabs(a - b) <= 16 * DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

如果比较性能对您非常重要,并且您知道值的范围,那么您应该使用定点数字。

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

浮点数等于(==)

我也更喜欢减法技术,而不是依赖于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!