简体   繁体   English

浮动和双重比较最有效的方法是什么?

[英]What is the most effective way for float and double comparison?

What would be the most efficient way to compare two double or two float values?比较两个double精度值或两个float值的最有效方法是什么?

Simply doing this is not correct:简单地这样做是不正确的:

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

But something like:但是像:

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

Seems to waste processing.好像是废物处理。

Does anyone know a smarter float comparer?有谁知道更智能的浮动比较器?

Be extremely careful using any of the other suggestions.使用任何其他建议时要非常小心。 It all depends on context.这一切都取决于上下文。

I have spent a long time tracing bugs in a system that presumed a==b if |ab|<epsilon .我花了很长时间跟踪假定a==b if |ab|<epsilon的系统中的错误。 The underlying problems were:潜在的问题是:

  1. The implicit presumption in an algorithm that if a==b and b==c then a==c .算法中的隐含假设,如果a==bb==ca==c

  2. Using the same epsilon for lines measured in inches and lines measured in mils (.001 inch).对以英寸为单位的线和以密耳(0.001 英寸)为单位的线使用相同的 epsilon。 That is a==b but 1000a!=1000b .那是a==b1000a!=1000b (This is why AlmostEqual2sComplement asks for the epsilon or max ULPS). (这就是AlmostEqual2sComplement要求epsilon 或max ULPS 的原因)。

  3. The use of the same epsilon for both the cosine of angles and the length of lines!对角的余弦和线的长度使用相同的 epsilon!

  4. Using such a compare function to sort items in a collection.使用这样的比较函数对集合中的项目进行排序。 (In this case using the builtin C++ operator == for doubles produced correct results.) (在这种情况下,使用内置 C++ 运算符==进行双精度运算会产生正确的结果。)

Like I said: it all depends on context and the expected size of a and b .就像我说的:这完全取决于上下文以及ab的预期大小。

By the way, std::numeric_limits<double>::epsilon() is the "machine epsilon".顺便说一句, std::numeric_limits<double>::epsilon()是“机器 epsilon”。 It is the difference between 1.0 and the next value representable by a double.它是1.0和下一个可以用双精度表示的值之间的差。 I guess that it could be used in the compare function but only if the expected values are less than 1. (This is in response to @cdv's answer...)我猜它可以在比较函数中使用,但前提是预期值小于 1。(这是对@cdv 的回答......)

Also, if you basically have int arithmetic in doubles (here we use doubles to hold int values in certain cases) your arithmetic will be correct.此外,如果您基本上在doubles数中有int算术(这里我们在某些情况下使用双精度值来保存 int 值)您的算术将是正确的。 For example 4.0/2.0 will be the same as 1.0+1.0 .例如4.0/2.0将与1.0+1.0相同。 This is as long as you do not do things that result in fractions ( 4.0/3.0 ) or do not go outside of the size of an int.只要您不做导致分数( 4.0/3.0 )或不超出 int 大小的事情。

The comparison with an epsilon value is what most people do (even in game programming).与 epsilon 值进行比较是大多数人所做的(即使在游戏编程中也是如此)。

You should change your implementation a little though:你应该稍微改变你的实现:

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

Edit: Christer has added a stack of great info on this topic on a recent blog post .编辑:克里斯特在最近的一篇博文中添加了大量关于这个主题的重要信息。 Enjoy.享受。

Comparing floating point numbers for depends on the context.比较浮点数取决于上下文。 Since even changing the order of operations can produce different results, it is important to know how "equal" you want the numbers to be.由于即使更改操作顺序也会产生不同的结果,因此了解您希望数字有多“相等”很重要。

Comparing floating point numbers by Bruce Dawson is a good place to start when looking at floating point comparison. Bruce Dawson 的比较浮点数是查看浮点比较的一个很好的起点。

The following definitions are from The art of computer programming by Knuth :以下定义来自Knuth 的计算机编程艺术

bool approximatelyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool essentiallyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyGreaterThan(float a, float b, float epsilon)
{
    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyLessThan(float a, float b, float epsilon)
{
    return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

Of course, choosing epsilon depends on the context, and determines how equal you want the numbers to be.当然,选择 epsilon 取决于上下文,并确定您希望数字有多相等。

Another method of comparing floating point numbers is to look at the ULP (units in last place) of the numbers.比较浮点数的另一种方法是查看数字的 ULP(最后一个单位)。 While not dealing specifically with comparisons, the paperWhat every computer scientist should know about floating point numbers is a good resource for understanding how floating point works and what the pitfalls are, including what ULP is.虽然没有专门处理比较,但这篇文章每个计算机科学家都应该了解浮点数是了解浮点数如何工作以及陷阱是什么(包括 ULP 是什么)的一个很好的资源。

I found that the Google C++ Testing Framework contains a nice cross-platform template-based implementation of AlmostEqual2sComplement which works on both doubles and floats.我发现Google C++ 测试框架包含一个很好的基于模板的AlmostEqual2sComplement 跨平台实现,它适用于双精度数和浮点数。 Given that it is released under the BSD license, using it in your own code should be no problem, as long as you retain the license.鉴于它是根据 BSD 许可证发布的,只要您保留许可证,在您自己的代码中使用它应该没有问题。 I extracted the below code from http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://github.com/google/googletest/blob/master/googletest/include/gtest/internal/gtest-internal.h and added the license on top.我从http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://github.com/google/googletest/blob中提取了以下代码/master/googletest/include/gtest/internal/gtest-internal.h并在顶部添加了许可证。

Be sure to #define GTEST_OS_WINDOWS to some value (or to change the code where it's used to something that fits your codebase - it's BSD licensed after all).请务必将#define GTEST_OS_WINDOWS 设置为某个值(或将其用于适合您的代码库的代码更改为适合您的代码库的代码——毕竟它是BSD 许可的)。

Usage example:使用示例:

double left  = // something
double right = // something
const FloatingPoint<double> lhs(left), rhs(right);

if (lhs.AlmostEquals(rhs)) {
  //they're equal!
}

Here's the code:这是代码:

// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)


// This template class serves as a compile-time function from size to
// type.  It maps a size in bytes to a primitive type with that
// size. e.g.
//
//   TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs.  Other types can be easily added in the future if need
// arises.
template <size_t size>
class TypeWithSize {
 public:
  // This prevents the user from using TypeWithSize<N> with incorrect
  // values of N.
  typedef void UInt;
};

// The specialization for size 4.
template <>
class TypeWithSize<4> {
 public:
  // unsigned int has size 4 in both gcc and MSVC.
  //
  // As base/basictypes.h doesn't compile on Windows, we cannot use
  // uint32, uint64, and etc here.
  typedef int Int;
  typedef unsigned int UInt;
};

// The specialization for size 8.
template <>
class TypeWithSize<8> {
 public:
#if GTEST_OS_WINDOWS
  typedef __int64 Int;
  typedef unsigned __int64 UInt;
#else
  typedef long long Int;  // NOLINT
  typedef unsigned long long UInt;  // NOLINT
#endif  // GTEST_OS_WINDOWS
};


// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison.  (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly.  Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
//   The most-significant bit being the leftmost, an IEEE
//   floating-point looks like
//
//     sign_bit exponent_bits fraction_bits
//
//   Here, sign_bit is a single bit that designates the sign of the
//   number.
//
//   For float, there are 8 exponent bits and 23 fraction bits.
//
//   For double, there are 11 exponent bits and 52 fraction bits.
//
//   More details can be found at
//   http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
//   RawType: the raw floating-point type (either float or double)
template <typename RawType>
class FloatingPoint {
 public:
  // Defines the unsigned integer type that has the same size as the
  // floating point number.
  typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;

  // Constants.

  // # of bits in a number.
  static const size_t kBitCount = 8*sizeof(RawType);

  // # of fraction bits in a number.
  static const size_t kFractionBitCount =
    std::numeric_limits<RawType>::digits - 1;

  // # of exponent bits in a number.
  static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;

  // The mask for the sign bit.
  static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);

  // The mask for the fraction bits.
  static const Bits kFractionBitMask =
    ~static_cast<Bits>(0) >> (kExponentBitCount + 1);

  // The mask for the exponent bits.
  static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);

  // How many ULP's (Units in the Last Place) we want to tolerate when
  // comparing two numbers.  The larger the value, the more error we
  // allow.  A 0 value means that two numbers must be exactly the same
  // to be considered equal.
  //
  // The maximum error of a single floating-point operation is 0.5
  // units in the last place.  On Intel CPU's, all floating-point
  // calculations are done with 80-bit precision, while double has 64
  // bits.  Therefore, 4 should be enough for ordinary use.
  //
  // See the following article for more details on ULP:
  // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
  static const size_t kMaxUlps = 4;

  // Constructs a FloatingPoint from a raw floating-point number.
  //
  // On an Intel CPU, passing a non-normalized NAN (Not a Number)
  // around may change its bits, although the new value is guaranteed
  // to be also a NAN.  Therefore, don't expect this constructor to
  // preserve the bits in x when x is a NAN.
  explicit FloatingPoint(const RawType& x) { u_.value_ = x; }

  // Static methods

  // Reinterprets a bit pattern as a floating-point number.
  //
  // This function is needed to test the AlmostEquals() method.
  static RawType ReinterpretBits(const Bits bits) {
    FloatingPoint fp(0);
    fp.u_.bits_ = bits;
    return fp.u_.value_;
  }

  // Returns the floating-point number that represent positive infinity.
  static RawType Infinity() {
    return ReinterpretBits(kExponentBitMask);
  }

  // Non-static methods

  // Returns the bits that represents this number.
  const Bits &bits() const { return u_.bits_; }

  // Returns the exponent bits of this number.
  Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }

  // Returns the fraction bits of this number.
  Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }

  // Returns the sign bit of this number.
  Bits sign_bit() const { return kSignBitMask & u_.bits_; }

  // Returns true iff this is NAN (not a number).
  bool is_nan() const {
    // It's a NAN if the exponent bits are all ones and the fraction
    // bits are not entirely zeros.
    return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
  }

  // Returns true iff this number is at most kMaxUlps ULP's away from
  // rhs.  In particular, this function:
  //
  //   - returns false if either number is (or both are) NAN.
  //   - treats really large numbers as almost equal to infinity.
  //   - thinks +0.0 and -0.0 are 0 DLP's apart.
  bool AlmostEquals(const FloatingPoint& rhs) const {
    // The IEEE standard says that any comparison operation involving
    // a NAN must return false.
    if (is_nan() || rhs.is_nan()) return false;

    return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
        <= kMaxUlps;
  }

 private:
  // The data type used to store the actual floating-point number.
  union FloatingPointUnion {
    RawType value_;  // The raw floating-point number.
    Bits bits_;      // The bits that represent the number.
  };

  // Converts an integer from the sign-and-magnitude representation to
  // the biased representation.  More precisely, let N be 2 to the
  // power of (kBitCount - 1), an integer x is represented by the
  // unsigned number x + N.
  //
  // For instance,
  //
  //   -N + 1 (the most negative number representable using
  //          sign-and-magnitude) is represented by 1;
  //   0      is represented by N; and
  //   N - 1  (the biggest number representable using
  //          sign-and-magnitude) is represented by 2N - 1.
  //
  // Read http://en.wikipedia.org/wiki/Signed_number_representations
  // for more details on signed number representations.
  static Bits SignAndMagnitudeToBiased(const Bits &sam) {
    if (kSignBitMask & sam) {
      // sam represents a negative number.
      return ~sam + 1;
    } else {
      // sam represents a positive number.
      return kSignBitMask | sam;
    }
  }

  // Given two numbers in the sign-and-magnitude representation,
  // returns the distance between them as an unsigned number.
  static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
                                                     const Bits &sam2) {
    const Bits biased1 = SignAndMagnitudeToBiased(sam1);
    const Bits biased2 = SignAndMagnitudeToBiased(sam2);
    return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
  }

  FloatingPointUnion u_;
};

EDIT: This post is 4 years old.编辑:这篇文章已有 4 年历史。 It's probably still valid, and the code is nice, but some people found improvements.它可能仍然有效,并且代码很好,但有些人发现了改进。 Best go get the latest version of AlmostEquals right from the Google Test source code, and not the one I pasted up here.最好直接从 Google 测试源代码中获取最新版本的AlmostEquals ,而不是我在这里粘贴的那个。

For a more in depth approach read Comparing floating point numbers .如需更深入的方法,请阅读比较浮点数 Here is the code snippet from that link:这是该链接的代码片段:

// 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;    
}

Realizing this is an old thread but this article is one of the most straight forward ones I have found on comparing floating point numbers and if you want to explore more it has more detailed references as well and it the main site covers a complete range of issues dealing with floating point numbers The Floating-Point Guide :Comparison .意识到这是一个老话题,但这篇文章是我在比较浮点数时发现的最直接的文章之一,如果你想探索更多,它也有更详细的参考资料,它的主站点涵盖了一系列完整的问题处理浮点数浮点指南:比较

We can find a somewhat more practical article in Floating-point tolerances revisited and notes there is absolute tolerance test, which boils down to this in C++:我们可以在Floating-point tolerances revisited中找到一篇更实用的文章,并指出存在绝对容差测试,这在 C++ 中归结为:

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

and relative tolerance test:相对公差测试:

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 ;
}

The article notes that the absolute test fails when x and y are large and fails in the relative case when they are small.文章指出,当xy很大时,绝对测试失败,而当它们很小时,相对测试失败。 Assuming he absolute and relative tolerance is the same a combined test would look like this:假设绝对容差和相对容差相同,则组合测试将如下所示:

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 ;
}

I ended up spending quite some time going through material in this great thread.我最终花了相当长的时间在这个伟大的线程中浏览材料。 I doubt everyone wants to spend so much time so I would highlight the summary of what I learned and the solution I implemented.我怀疑每个人都想花这么多时间,所以我会强调我学到的东西和我实施的解决方案的总结。

Quick Summary快速总结

  1. Is 1e-8 approximately same as 1e-16? 1e-8 与 1e-16 大致相同吗? If you are looking at noisy sensor data then probably yes but if you are doing molecular simulation then may be not!如果您正在查看嘈杂的传感器数据,那么可能是的,但如果您正在进行分子模拟,那么可能不是! Bottom line: You always need to think of tolerance value in context of specific function call and not just make it generic app-wide hard-coded constant.底线:您始终需要在特定函数调用的上下文中考虑差值,而不仅仅是使其成为通用应用程序范围内的硬编码常量。
  2. For general library functions, it's still nice to have parameter with default tolerance .对于通用库函数,具有默认容差的参数仍然很好。 A typical choice is numeric_limits::epsilon() which is same as FLT_EPSILON in float.h.一个典型的选择是numeric_limits::epsilon() ,它与 ​​float.h 中的 FLT_EPSILON 相同。 This is however problematic because epsilon for comparing values like 1.0 is not same as epsilon for values like 1E9.然而,这是有问题的,因为比较像 1.0 这样的值的 epsilon 与比较像 1E9 这样的值的 epsilon 是不同的。 The FLT_EPSILON is defined for 1.0. FLT_EPSILON 是为 1.0 定义的。
  3. The obvious implementation to check if number is within tolerance is fabs(ab) <= epsilon however this doesn't work because default epsilon is defined for 1.0.检查数字是否在公差范围内的明显实现是fabs(ab) <= epsilon但这不起作用,因为默认 epsilon 是为 1.0 定义的。 We need to scale epsilon up or down in terms of a and b.我们需要根据 a 和 b 来放大或缩小 epsilon。
  4. There are two solution to this problem: either you set epsilon proportional to max(a,b) or you can get next representable numbers around a and then see if b falls into that range.这个问题有两种解决方案:要么将 epsilon 设置为与max(a,b)成比例,要么可以在 a 周围获得下一个可表示的数字,然后查看 b 是否落在该范围内。 The former is called "relative" method and later is called ULP method.前者称为“相对”方法,后者称为 ULP 方法。
  5. Both methods actually fails anyway when comparing with 0. In this case, application must supply correct tolerance.与 0 比较时,这两种方法实际上都失败了。在这种情况下,应用程序必须提供正确的容差。

Utility Functions Implementation (C++11)实用功能实现 (C++11)

//implements relative method - do not use for comparing with zero
//use this most of the time, tolerance needs to be meaningful in your context
template<typename TReal>
static bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = std::fabs(a - b);
    if (diff <= tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//supply tolerance that is meaningful in your context
//for example, default tolerance may not work if you are comparing double with float
template<typename TReal>
static bool isApproximatelyZero(TReal a, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    if (std::fabs(a) <= tolerance)
        return true;
    return false;
}


//use this when you want to be on safe side
//for example, don't start rover unless signal is above 1
template<typename TReal>
static bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff < tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}
template<typename TReal>
static bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff > tolerance)
        return true;

    if (diff > std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//implements ULP method
//use this when you are only concerned about floating point precision issue
//for example, if you want to see if a is 1.0 by checking if its within
//10 closest representable floating point numbers around 1.0.
template<typename TReal>
static bool isWithinPrecisionInterval(TReal a, TReal b, unsigned int interval_size = 1)
{
    TReal min_a = a - (a - std::nextafter(a, std::numeric_limits<TReal>::lowest())) * interval_size;
    TReal max_a = a + (std::nextafter(a, std::numeric_limits<TReal>::max()) - a) * interval_size;

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

The portable way to get epsilon in C++ is在 C++ 中获取 epsilon 的可移植方式是

#include <limits>
std::numeric_limits<double>::epsilon()

Then the comparison function becomes那么比较函数就变成了

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

The code you wrote is bugged :你写的代码有问题:

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

The correct code would be :正确的代码是:

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

(...and yes this is different) (......是的,这是不同的)

I wonder if fabs wouldn't make you lose lazy evaluation in some case.我想知道在某些情况下晶圆厂是否不会让你失去懒惰的评估。 I would say it depends on the compiler.我会说这取决于编译器。 You might want to try both.您可能想尝试两者。 If they are equivalent in average, take the implementation with fabs.如果它们平均相等,请使用 fabs 实施。

If you have some info on which of the two float is more likely to be bigger than then other, you can play on the order of the comparison to take better advantage of the lazy evaluation.如果您有一些关于两个浮点数中的哪一个更可能比另一个更大的信息,您可以按照比较的顺序进行操作,以更好地利用惰性评估。

Finally you might get better result by inlining this function.最后,通过内联这个函数,你可能会得到更好的结果。 Not likely to improve much though...虽然不太可能改善...

Edit: OJ, thanks for correcting your code.编辑:OJ,感谢您更正您的代码。 I erased my comment accordingly我相应地删除了我的评论

`return fabs(a - b) < EPSILON; `return fabs(a - b) < EPSILON;

This is fine if:这很好,如果:

  • the order of magnitude of your inputs don't change much您输入的数量级变化不大
  • very small numbers of opposite signs can be treated as equal非常少量的相反符号可以被视为相等

But otherwise it'll lead you into trouble.但否则它会给你带来麻烦。 Double precision numbers have a resolution of about 16 decimal places.双精度数字的分辨率约为小数点后 16 位。 If the two numbers you are comparing are larger in magnitude than EPSILON*1.0E16, then you might as well be saying:如果您要比较的两个数字的幅度大于 EPSILON*1.0E16,那么您不妨说:

return a==b;

I'll examine a different approach that assumes you need to worry about the first issue and assume the second is fine your application.我将研究另一种方法,假设您需要担心第一个问题并假设第二个问题对您的应用程序来说很好。 A solution would be something like:解决方案类似于:

#define VERYSMALL  (1.0E-150)
#define EPSILON    (1.0E-8)
bool AreSame(double a, double b)
{
    double absDiff = fabs(a - b);
    if (absDiff < VERYSMALL)
    {
        return true;
    }

    double maxAbs  = max(fabs(a) - fabs(b));
    return (absDiff/maxAbs) < EPSILON;
}

This is expensive computationally, but it is sometimes what is called for.这在计算上是昂贵的,但有时是需要的。 This is what we have to do at my company because we deal with an engineering library and inputs can vary by a few dozen orders of magnitude.这是我们在我公司必须做的,因为我们处理的是一个工程库,输入可能会有几十个数量级的变化。

Anyway, the point is this (and applies to practically every programming problem): Evaluate what your needs are, then come up with a solution to address your needs -- don't assume the easy answer will address your needs.无论如何,重点是这一点(并且几乎适用于所有编程问题):评估您的需求,然后提出解决方案来满足您的需求——不要假设简单的答案就能满足您的需求。 If after your evaluation you find that fabs(ab) < EPSILON will suffice, perfect -- use it!如果在您的评估之后您发现fabs(ab) < EPSILON就足够了,那么完美——使用它! But be aware of its shortcomings and other possible solutions too.但也要注意它的缺点和其他可能的解决方案。

As others have pointed out, using a fixed-exponent epsilon (such as 0.0000001) will be useless for values away from the epsilon value.正如其他人指出的那样,对于远离 epsilon 值的值,使用固定指数 epsilon(例如 0.0000001)将毫无用处 For example, if your two values are 10000.000977 and 10000, then there are NO 32-bit floating-point values between these two numbers -- 10000 and 10000.000977 are as close as you can possibly get without being bit-for-bit identical.例如,如果您的两个值是 10000.000977 和 10000,那么这两个数字之间没有32 位浮点值 - 10000 和 10000.000977 在不逐位相同的情况下尽可能接近。 Here, an epsilon of less than 0.0009 is meaningless;在这里,小于 0.0009 的 epsilon 是没有意义的; you might as well use the straight equality operator.您不妨使用直接相等运算符。

Likewise, as the two values approach epsilon in size, the relative error grows to 100%.同样,当这两个值的大小接近 epsilon 时,相对误差会增长到 100%。

Thus, trying to mix a fixed point number such as 0.00001 with floating-point values (where the exponent is arbitrary) is a pointless exercise.因此,尝试将诸如 0.00001 之类的定点数与浮点值(其中指数是任意的)混合是毫无意义的。 This will only ever work if you can be assured that the operand values lie within a narrow domain (that is, close to some specific exponent), and if you properly select an epsilon value for that specific test.只有当您可以确保操作数值位于一个狭窄的域内(即接近某个特定指数)并且您为该特定测试正确选择了一个 epsilon 值时,这才会起作用。 If you pull a number out of the air ("Hey! 0.00001 is small, so that must be good!"), you're doomed to numerical errors.如果你从空中拉出一个数字(“嘿!0.00001 很小,所以那一定很好!”),你注定会出现数字错误。 I've spent plenty of time debugging bad numerical code where some poor schmuck tosses in random epsilon values to make yet another test case work.我花了很多时间调试糟糕的数字代码,其中一些糟糕的 schmuck 会在随机 epsilon 值中折腾以使另一个测试用例工作。

If you do numerical programming of any kind and believe you need to reach for fixed-point epsilons, READ BRUCE'S ARTICLE ON COMPARING FLOATING-POINT NUMBERS .如果您进行任何类型的数值编程并认为您需要使用定点 epsilon,请阅读 BRUCE 关于比较浮点数的文章

Comparing Floating Point Numbers 比较浮点数

Here's proof that using std::numeric_limits::epsilon() is not the answer — it fails for values greater than one:这是使用std::numeric_limits::epsilon()不是答案的证据——它对于大于一的值会失败:

Proof of my comment above:我上面评论的证明:

#include <stdio.h>
#include <limits>

double ItoD (__int64 x) {
    // Return double from 64-bit hexadecimal representation.
    return *(reinterpret_cast<double*>(&x));
}

void test (__int64 ai, __int64 bi) {
    double a = ItoD(ai), b = ItoD(bi);
    bool close = std::fabs(a-b) < std::numeric_limits<double>::epsilon();
    printf ("%.16f and %.16f %s close.\n", a, b, close ? "are " : "are not");
}

int main()
{
    test (0x3fe0000000000000L,
          0x3fe0000000000001L);

    test (0x3ff0000000000000L,
          0x3ff0000000000001L);
}

Running yields this output:运行产生以下输出:

0.5000000000000000 and 0.5000000000000001 are  close.
1.0000000000000000 and 1.0000000000000002 are not close.

Note that in the second case (one and just larger than one), the two input values are as close as they can possibly be, and still compare as not close.请注意,在第二种情况下(一且仅大于一),两个输入值尽可能接近,但仍比较不接近。 Thus, for values greater than 1.0, you might as well just use an equality test.因此,对于大于 1.0 的值,您不妨只使用相等测试。 Fixed epsilons will not save you when comparing floating-point values.比较浮点值时,固定的 epsilon 不会为您节省时间。

Qt implements two functions, maybe you can learn from them: Qt实现了两个功能,或许你可以借鉴一下:

static inline bool qFuzzyCompare(double p1, double p2)
{
    return (qAbs(p1 - p2) <= 0.000000000001 * qMin(qAbs(p1), qAbs(p2)));
}

static inline bool qFuzzyCompare(float p1, float p2)
{
    return (qAbs(p1 - p2) <= 0.00001f * qMin(qAbs(p1), qAbs(p2)));
}

And you may need the following functions, since您可能需要以下功能,因为

Note that comparing values where either p1 or p2 is 0.0 will not work, nor does comparing values where one of the values is NaN or infinity.请注意,比较 p1 或 p2 为 0.0 的值将不起作用,比较其中一个值为 NaN 或无穷大的值也不起作用。 If one of the values is always 0.0, use qFuzzyIsNull instead.如果其中一个值始终为 0.0,请改用 qFuzzyIsNull。 If one of the values is likely to be 0.0, one solution is to add 1.0 to both values.如果其中一个值可能为 0.0,则一种解决方案是将两个值都加 1.0。

static inline bool qFuzzyIsNull(double d)
{
    return qAbs(d) <= 0.000000000001;
}

static inline bool qFuzzyIsNull(float f)
{
    return qAbs(f) <= 0.00001f;
}

Unfortunately, even your "wasteful" code is incorrect.不幸的是,即使您的“浪费”代码也不正确。 EPSILON is the smallest value that could be added to 1.0 and change its value. EPSILON 是可以添加到1.0并更改其值的最小值。 The value 1.0 is very important — larger numbers do not change when added to EPSILON.1.0非常重要——添加到 EPSILON 时较大的数字不会改变。 Now, you can scale this value to the numbers you are comparing to tell whether they are different or not.现在,您可以将此值缩放到您要比较的数字,以判断它们是否不同。 The correct expression for comparing two doubles is:比较两个双精度的正确表达式是:

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

This is at a minimum.这是最低限度的。 In general, though, you would want to account for noise in your calculations and ignore a few of the least significant bits, so a more realistic comparison would look like:不过,一般来说,您希望在计算中考虑噪声并忽略一些最低有效位,因此更现实的比较如下所示:

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

If comparison performance is very important to you and you know the range of your values, then you should use fixed-point numbers instead.如果比较性能对您非常重要并且您知道值的范围,那么您应该改用定点数。

General-purpose comparison of floating-point numbers is generally meaningless.浮点数的通用比较通常没有意义。 How to compare really depends on a problem at hand.如何比较实际上取决于手头的问题。 In many problems, numbers are sufficiently discretized to allow comparing them within a given tolerance.在许多问题中,数字被充分离散化以允许在给定的容差范围内进行比较。 Unfortunately, there are just as many problems, where such trick doesn't really work.不幸的是,同样有很多问题,这种技巧并没有真正奏效。 For one example, consider working with a Heaviside (step) function of a number in question (digital stock options come to mind) when your observations are very close to the barrier.例如,当您的观察非常接近障碍时,请考虑使用相关数字的 Heaviside(步进)函数(想到数字股票期权)。 Performing tolerance-based comparison wouldn't do much good, as it would effectively shift the issue from the original barrier to two new ones.执行基于容差的比较不会有多大好处,因为它会有效地将问题从原来的障碍转移到两个新的障碍。 Again, there is no general-purpose solution for such problems and the particular solution might require going as far as changing the numerical method in order to achieve stability.同样,此类问题没有通用解决方案,特定解决方案可能需要更改数值方法以实现稳定性。

You have to do this processing for floating point comparison, since float's can't be perfectly compared like integer types.您必须对浮点比较进行此处理,因为浮点数不能像整数类型那样完美地比较。 Here are functions for the various comparison operators.以下是各种比较运算符的函数。

Floating Point Equal to ( == )浮点等于 ( == )

I also prefer the subtraction technique rather than relying on fabs() or abs() , but I'd have to speed profile it on various architectures from 64-bit PC to ATMega328 microcontroller (Arduino) to really see if it makes much of a performance difference.我也更喜欢减法技术,而不是依赖fabs()abs() ,但我必须在从 64 位 PC 到 ATMega328 微控制器 (Arduino) 的各种架构上快速分析它,才能真正看到它是否能起到很大的作用性能差异。

So, let's forget about all this absolute value stuff and just do some subtraction and comparison!所以,让我们忘记所有这些绝对值的东西,只做一些减法和比较!

Modified from Microsoft's example here :微软的例子修改这里

/// @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);
}

Example usage:示例用法:

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

I'm not entirely sure, but it seems to me some of the criticisms of the epsilon-based approach, as described in the comments below this highly-upvoted answer , can be resolved by using a variable epsilon, scaled according to the floating point values being compared, like this:我不完全确定,但在我看来,对基于 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);

This way, the epsilon value scales with the floating point values and is therefore never so small of a value that it becomes insignificant.这样,epsilon 值随浮点值缩放,因此永远不会太小以至于变得微不足道。

For completeness, let's add the rest:为了完整起见,让我们添加其余部分:

Greater than ( > ), and less than ( < ):大于 ( > ) 和小于 ( < ):

/// @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;
}

Greater than or equal to ( >= ), and less than or equal to ( <= )大于或等于 ( >= ) 且小于或等于 ( <= )

/// @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;
}

Additional improvements:其他改进:

  1. 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 . C++ 中epsilon的一个好的默认值是std::numeric_limits<T>::epsilon() ,其计算结果为0FLT_EPSILONDBL_EPSILONLDBL_EPSILON See here: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon .请参阅此处: 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 .您还可以看到FLT_EPSILONDBL_EPSILONLDBL_EPSILONfloat.h标头。
    1. See https://en.cppreference.com/w/cpp/header/cfloat and请参阅https://en.cppreference.com/w/cpp/header/cfloat
    2. https://www.cplusplus.com/reference/cfloat/ https://www.cplusplus.com/reference/cfloat/
  2. 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.您可以改为模板函数,以处理所有浮点类型: floatdoublelong double ,并通过模板内的static_assert()对这些类型进行类型检查
  3. Scaling the epsilon value is a good idea to ensure it works for really large and really small a and b values.缩放epsilon值是一个好主意,以确保它适用于非常大和非常小ab值。 This article recommends and explains it: http://realtimecollisiondetection.net/blog/?p=89 .本文推荐并说明: 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.因此,您应该通过等于max(1.0, abs(a), abs(b))的缩放值来缩放 epsilon,正如那篇文章所解释的那样。 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.否则,随着a和/或b幅度的增加,epsilon 最终会相对于那些值变得非常小,以至于它会丢失在浮点误差中。 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.但是,使用1.0作为 epsilon 的最小允许比例因子还可以确保对于非常小的ab值,epsilon 本身不会被缩放得太小,以至于它也会丢失在浮点错误中。 So, we limit the minimum scaling factor to 1.0 .因此,我们将最小比例因子限制为1.0
  4. 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);例如:如果您将所有独立函数放入名为float_comparison的命名空间中,那么您可以像这样访问is_eq()函数,例如: float_comparison::is_eq(1.0, 1.5); . .
  5. It might also be nice to add comparisons against zero, not just comparisons between two values.添加与零的比较也可能很好,而不仅仅是两个值之间的比较。
  6. 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

See also:也可以看看:

  1. The macro forms of some of the functions above in my repo here: utilities.h .我的仓库中上述一些函数的宏形式: utilities.h
    1. 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 . 2020 年 11 月 29 日更新:这是一项正在进行中的工作,准备好后我将把它作为一个单独的答案,但我已经在此文件中为 C 中的所有函数生成了一个更好的缩放 epsilon 版本这里: 实用程序.c Take a look.看一看。
  2. ADDITIONAL READING I need to do now have done: Floating-point tolerances revisited, by Christer Ericson .我现在需要做补充阅读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!它讨论了缩放 epsilon 以确保它永远不会陷入浮点错误,即使对于非常大的a和/或b值也是如此!

My class based on previously posted answers.我的课程基于以前发布的答案。 Very similar to Google's code but I use a bias which pushes all NaN values above 0xFF000000.与谷歌的代码非常相似,但我使用了一个将所有 NaN 值推到 0xFF000000 以上的偏差。 That allows a faster check for NaN.这样可以更快地检查 NaN。

This code is meant to demonstrate the concept, not be a general solution.此代码旨在演示该概念,而不是通用解决方案。 Google's code already shows how to compute all the platform specific values and I didn't want to duplicate all that. Google 的代码已经展示了如何计算所有平台特定的值,我不想重复所有这些。 I've done limited testing on this code.我已经对此代码进行了有限的测试。

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 );
                  }
};

I'd be very wary of any of these answers that involves floating point subtraction (eg, fabs(ab) < epsilon).我会非常警惕任何涉及浮点减法的答案(例如,fabs(ab) < epsilon)。 First, the floating point numbers become more sparse at greater magnitudes and at high enough magnitudes where the spacing is greater than epsilon, you might as well just be doing a == b.首先,浮点数在更大的幅度和足够高的幅度(间距大于ε)处变得更加稀疏,您不妨只做a == b。 Second, subtracting two very close floating point numbers (as these will tend to be, given that you're looking for near equality) is exactly how you get catastrophic cancellation .其次,减去两个非常接近的浮点数(因为这些往往是,因为您正在寻找接近相等)正是您获得灾难性取消的方式。

While not portable, I think grom's answer does the best job of avoiding these issues.虽然不可移植,但我认为 grom 的答案在避免这些问题方面做得最好。

There are actually cases in numerical software where you want to check whether two floating point numbers are exactly equal.在数值软件中实际上存在您想要检查两个浮点数是否完全相等的情况。 I posted this on a similar question我在一个类似的问题上发布了这个

https://stackoverflow.com/a/10973098/1447411 https://stackoverflow.com/a/10973098/1447411

So you can not say that "CompareDoubles1" is wrong in general.所以你不能说“CompareDoubles1”通常是错误的。

In terms of the scale of quantities:从数量规模来看:

If epsilon is the small fraction of the magnitude of quantity (ie relative value) in some certain physical sense and A and B types is comparable in the same sense, than I think, that the following is quite correct:如果epsilon是某种物理意义上的量值(即相对值)的一小部分,并且AB类型在同一意义上是可比较的,那么我认为以下是完全正确的:

#include <limits>
#include <iomanip>
#include <iostream>

#include <cmath>
#include <cstdlib>
#include <cassert>

template< typename A, typename B >
inline
bool close_enough(A const & a, B const & b,
                  typename std::common_type< A, B >::type const & epsilon)
{
    using std::isless;
    assert(isless(0, epsilon)); // epsilon is a part of the whole quantity
    assert(isless(epsilon, 1));
    using std::abs;
    auto const delta = abs(a - b);
    auto const x = abs(a);
    auto const y = abs(b);
    // comparable generally and |a - b| < eps * (|a| + |b|) / 2
    return isless(epsilon * y, x) && isless(epsilon * x, y) && isless((delta + delta) / (x + y), epsilon);
}

int main()
{
    std::cout << std::boolalpha << close_enough(0.9, 1.0, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 1.1, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.1,    1.2,    0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0001, 1.0002, 0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 0.01, 0.1) << std::endl;
    return EXIT_SUCCESS;
}

In a more generic way:以更通用的方式:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}

Note:笔记:
As pointed out by @SirGuy, this approach is flawed.正如@SirGuy 所指出的,这种方法是有缺陷的。 I am leaving this answer here as an example not to follow.我把这个答案留在这里作为一个例子,不要效仿。

I use this code:我使用这段代码:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }

Some experiments with answers. 一些有答案的实验。

#ifndef FLOATING_POINT_MATH_H
#define FLOATING_POINT_MATH_H

#ifdef _MSC_VER
#pragma once
#endif

#include <type_traits>
#include <cmath>

/** Floating point math functions with custom tolerance

@param float, double, long double.
@return bool test result.

is_approximately_equal(a, b, tolerance)
is_essentialy_equal(a, b, tolerance)
is_approximately_zero(a, tolerance)
is_definitely_greater_than(a, b, tolerance)
is_definitely_less_than(a, b, tolerance)
is_within_tolerance(a, b, tolerance)
*/

namespace
{
    template<typename T> constexpr T default_tolerance = std::numeric_limits<T>::epsilon();
    template<typename T> using Test_floating_t = typename std::enable_if<std::is_floating_point<T>::value>::type;
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_approximately_equal(const T a, const T b, const T tolerance = default_tolerance<T>) noexcept
{
    return std::fabs(a - b) <= ((std::fabs(a) < std::fabs(b) ? std::fabs(b) : std::fabs(a)) * tolerance);
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_essentialy_equal(const T a, const T b, const T tolerance = default_tolerance<T>) noexcept
{
    return std::fabs(a - b) <= ((std::fabs(a) > std::fabs(b) ? std::fabs(b) : std::fabs(a)) * tolerance);
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_approximately_zero(const T a, const T tolerance = default_tolerance<T>) noexcept
{
    return std::fabs(a) <= tolerance;
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_definitely_greater_than(const T a, const T b, const T tolerance = default_tolerance<T>) noexcept
{
    return (a - b) > ((std::fabs(a) < std::fabs(b) ? std::fabs(b) : std::fabs(a)) * tolerance);
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_definitely_less_than(const T a, const T b, const T tolerance = default_tolerance<T>) noexcept
{
    return (b - a) > ((std::fabs(a) < std::fabs(b) ? std::fabs(b) : std::fabs(a)) * tolerance);
}



template<typename T, typename = Test_floating_t<T>> inline constexpr 
bool is_within_tolerance(const T a, const T b, const T tolerance = default_tolerance<T>) noexcept
{
    return std::fabs(a - b) <= tolerance;
}



#endif

//is_approximately_equal(0.101, 0.1, 0.01); // true
//is_essentialy_equal(0.1, 0.1, 0.01); // true
//is_essentialy_equal(0.101, 0.1, 0.01); // false
//is_essentialy_equal(0.101, 0.101, 0.01); // true
//is_approximately_equal(1, 1); // No instance matches arg...

Found another interesting implementation on: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon发现另一个有趣的实现: https ://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

#include <cmath>
#include <limits>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <algorithm>



template<class T>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
    almost_equal(T x, T y, int ulp)
{
    // the machine epsilon has to be scaled to the magnitude of the values used
    // and multiplied by the desired precision in ULPs (units in the last place)
    return std::fabs(x-y) <= std::numeric_limits<T>::epsilon() * std::fabs(x+y) * ulp
        // unless the result is subnormal
        || std::fabs(x-y) < std::numeric_limits<T>::min();
}

int main()
{
    double d1 = 0.2;
    double d2 = 1 / std::sqrt(5) / std::sqrt(5);
    std::cout << std::fixed << std::setprecision(20) 
        << "d1=" << d1 << "\nd2=" << d2 << '\n';

    if(d1 == d2)
        std::cout << "d1 == d2\n";
    else
        std::cout << "d1 != d2\n";

    if(almost_equal(d1, d2, 2))
        std::cout << "d1 almost equals d2\n";
    else
        std::cout << "d1 does not almost equal d2\n";
}

I use this code.我使用此代码。 Unlike the above answers this allows one to give a abs_relative_error that is explained in the comments of the code.与上述答案不同,这允许人们给出代码注释中解释的abs_relative_error

The first version compares complex numbers, so that the error can be explained in terms of the angle between two "vectors" of the same length in the complex plane (which gives a little insight).第一个版本比较复数,因此可以用复平面中相同长度的两个“矢量”之间的角度来解释错误(这提供了一点见解)。 Then from there the correct formula for two real numbers follows.然后从那里得出两个实数的正确公式。

https://github.com/CarloWood/ai-utils/blob/master/almost_equal.h https://github.com/CarloWood/ai-utils/blob/master/almost_equal.h

The latter then is那么后者是

template<class T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
   almost_equal(T x, T y, T const abs_relative_error)
{
  return 2 * std::abs(x - y) <= abs_relative_error * std::abs(x + y);
}

where abs_relative_error is basically (twice) the absolute value of what comes closest to being defined in the literature: a relative error.其中abs_relative_error基本上是(两倍)最接近文献中定义的绝对值:相对误差。 But that is just the choice of the name.但这只是名称的选择。

What it really is seen most clearly in the complex plane I think.我认为在复平面中最清楚地看到了它的真正含义。 If |x|如果 |x| = 1, and y lays in a circle around x with diameter abs_relative_error , then the two are considered equal. = 1,并且 y 位于 x 周围,直径为abs_relative_error ,则认为两者相等。

I use the following function for floating-point numbers comparison:我使用以下 function 进行浮点数比较:

bool approximatelyEqual(double a, double b)
{
  return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits<double>::epsilon());
}

It depends on how precise you want the comparison to be.这取决于您希望比较的精确程度。 If you want to compare for exactly the same number, then just go with ==.如果您想比较完全相同的数字,那么只需使用 ==。 (You almost never want to do this unless you actually want exactly the same number.) On any decent platform you can also do the following: (除非您实际上想要完全相同的数字,否则您几乎永远不想这样做。)在任何体面的平台上,您还可以执行以下操作:

diff= a - b; return fabs(diff)<EPSILON;

as fabs tends to be pretty fast.因为fabs往往很快。 By pretty fast I mean it is basically a bitwise AND, so it better be fast.相当快我的意思是它基本上是一个按位与,所以最好是快。

And integer tricks for comparing doubles and floats are nice but tend to make it more difficult for the various CPU pipelines to handle effectively.比较双精度和浮点数的整数技巧很好,但往往会使各种 CPU 管道更难以有效处理。 And it's definitely not faster on certain in-order architectures these days due to using the stack as a temporary storage area for values that are being used frequently.由于使用堆栈作为经常使用的值的临时存储区域,现在在某些有序架构上肯定不会更快。 (Load-hit-store for those who care.) (为那些关心的人加载命中商店。)

/// 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;
}

I used this function for my small project and it works, but note the following:我在我的小项目中使用了这个功能,它可以工作,但请注意以下几点:

Double precision error can create a surprise for you.双精度误差会给您带来惊喜。 Let's say epsilon = 1.0e-6, then 1.0 and 1.000001 should NOT be considered equal according to the above code, but on my machine the function considers them to be equal, this is because 1.000001 can not be precisely translated to a binary format, it is probably 1.0000009xxx.假设 epsilon = 1.0e-6,那么根据上面的代码,1.0 和 1.000001 不应该被认为是相等的,但是在我的机器上,函数认为它们是相等的,这是因为 1.000001 不能精确地转换为二进制格式,它可能是 1.0000009xxx。 I test it with 1.0 and 1.0000011 and this time I get the expected result.我用 1.0 和 1.0000011 测试它,这次我得到了预期的结果。

You cannot compare two double with a fixed EPSILON .您不能将两个double与固定的EPSILON进行比较。 Depending on the value of double , EPSILON varies.根据double的值, EPSILON会有所不同。

A better double comparison would be:更好的双重比较是:

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

My way may not be correct but useful我的方法可能不正确但很有用

Convert both float to strings and then do string compare将浮点数转换为字符串,然后进行字符串比较

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;

}

operator overlaoding can also be done运算符重载也可以

I write this for java, but maybe you find it useful.我是为 java 写的,但也许你觉得它很有用。 It uses longs instead of doubles, but takes care of NaNs, subnormals, etc.它使用 longs 而不是 doubles,但会处理 NaN、subnormals 等。

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; }

Keep in mind that after a number of floating-point operations, number can be very different from what we expect.请记住,经过多次浮点运算后,数字可能与我们预期的大不相同。 There is no code to fix that.没有代码可以解决这个问题。

This is another solution with lambda:这是 lambda 的另一种解决方案:

#include <cmath>
#include <limits>

auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };

How about this?这个怎么样?

template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }

I've seen various approaches - but never seen this, so I'm curious to hear of any comments too!我见过各种方法——但从未见过这种方法,所以我也很想听到任何评论!

In this version you check, that numbers differ from one another not more that for some fraction (say, 0.0001%):在您检查的这个版本中,这些数字彼此之间的差异并不超过某些分数(例如,0.0001%):

bool floatApproximatelyEquals(const float a, const float b) {
    if (b == 0.) return a == 0.; // preventing division by zero
    return abs(1. - a / b) < 1e-6;
}

Please note Sneftel 's comment about possible fraction limits for float.请注意Sneftel关于浮动可能的分数限制的评论。

Also note, that it differs from approach with absolute epsilons - here you don't bother about "order of magnitude" - numbers might be, say 1e100 , or 1e-100 , they will always be compared consistently, and you don't have to update epsilon for every case.另请注意,它与绝对 epsilon 的方法不同-在这里您不必担心“数量级”-数字可能是1e1001e-100 ,它们将始终进行一致比较,而您没有为每个案例更新 epsilon。

Why not perform bitwise XOR?为什么不执行按位异或? Two floating point numbers are equal if their corresponding bits are equal.如果它们的对应位相等,则两个浮点数相等。 I think, the decision to place the exponent bits before mantissa was made to speed up comparison of two floats.我认为,在尾数之前放置指数位的决定是为了加快两个浮点数的比较。 I think, many answers here are missing the point of epsilon comparison.我认为,这里的许多答案都缺少 epsilon 比较的重点。 Epsilon value only depends on to what precision floating point numbers are compared. Epsilon 值仅取决于比较精度的浮点数。 For example, after doing some arithmetic with floats you get two numbers: 2.5642943554342 and 2.5642943554345.例如,在对浮点数进行一些算术运算后,您会得到两个数字:2.5642943554342 和 2.5642943554345。 They are not equal, but for the solution only 3 decimal digits matter so then they are equal: 2.564 and 2.564.它们不相等,但对于解决方案,只有 3 个十进制数字很重要,所以它们相等:2.564 和 2.564。 In this case you choose epsilon equal to 0.001.在这种情况下,您选择 epsilon 等于 0.001。 Epsilon comparison is also possible with bitwise XOR.使用按位异或也可以进行 Epsilon 比较。 Correct me if I am wrong.如果我错了,请纠正我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在C中将浮点指针重铸为双指针的最有效方法是什么? - What's the most efficient way to recast a float pointer as a double pointer in C? 在c ++中读取文件最后一行的最有效方法是什么? - What is the most effective way to read the last line of a file in c++? 迭代std :: vector最有效的方法是什么?为什么? - What is the most effective way of iterating a std::vector and why? 在向量中移动项目的最有效方法是什么? - What is the most effective way to move items within a vector? 是围绕一个正确的方式进行浮动 - 双重比较 - Is to round a correct way for making float-double comparison 两次/浮点比较与精确值 - Double/float comparison with exact values 将浮点数与特定NAN值进行比较的最正确方法是什么? - What is the most correct way to compare float to a specific NAN value? 使用Android NDK将C ++对象传递给Java的最有效方法是什么? - What is the most effective way to pass a C++ Object to Java using Android NDK 有多少条线段将在一个平面内与水平线相交? 找到这个的最有效方法是什么 - how many line segments will intersect in a plane will intersect with a horizontal line ? what will be the most effective way to find this double,long double,float和float128的比较? - Comparison of double, long double, float and float128?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM