ChatGPT解决这个技术问题 Extra ChatGPT

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

比较两个 double 或两个 float 值的最有效方法是什么?

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

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

但是像:

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

好像是废物处理。

有谁知道更智能的浮动比较器?

>在函数的开头添加 ... 会更有效吗? <invoke Knuth>过早的优化是万恶之源。</invoke Knuth> 使用 abs(ab) < EPS 如上所述,它清晰易懂。
原始海报的实现唯一不理想的是它在 && 处包含一个额外的分支。 OJ 的答案是最佳的。 fabs 是一个内在函数,它是 x87 上的一条指令,我想几乎所有其他指令也是如此。已经接受OJ的回答了!
如果可以,请放弃浮点数并使用定点数。例如,使用 {fixed point} 毫米而不是 {floating point} 米。
“仅仅这样做是不正确的” - 这只是垃圾,当然使用 == 可能是完全正确的,但这完全取决于问题中未给出的上下文。在知道该上下文之前,== 仍然是“最有效的方式”

A
Andrew Stein

使用任何其他建议时要非常小心。这一切都取决于上下文。

我花了很长时间跟踪假定 a==b if |a-b|<epsilon 的系统中的错误。潜在的问题是:

算法中的隐含假设,如果 a==b 和 b==c 则 a==c。对以英寸为单位的线和以密耳(0.001 英寸)为单位的线使用相同的 epsilon。那是 a==b 但 1000a!=1000b。 (这就是AlmostEqual2sComplement 要求epsilon 或max ULPS 的原因)。对角的余弦和线的长度使用相同的 epsilon!使用这样的比较函数对集合中的项目进行排序。 (在这种情况下,使用内置 C++ 运算符 == 进行双精度运算会产生正确的结果。)

就像我说的:这完全取决于上下文以及 ab 的预期大小。

顺便说一句,std::numeric_limits<double>::epsilon() 是“机器 epsilon”。它是 1.0 和下一个可以用双精度表示的值之间的差。我猜它可以在比较函数中使用,但前提是预期值小于 1。(这是对@cdv 的回答......)

此外,如果您在 doubles 中基本上有 int 算术(这里我们在某些情况下使用双精度值来保存 int 值)您的算术将是正确的。例如 4.0/2.0 将与 1.0+1.0 相同。只要您不做导致分数 (4.0/3.0) 的事情或不超出 int 的大小。


+1 指出明显的(经常被忽略)。对于通用方法,您可以使 epsilon 相对于 fabs(a)+fabs(b),但要补偿 NaN、0 和和溢出,这会变得非常复杂。
一定有什么我不明白的。典型的 float/doubleMANTISSA x 2^ EXPepsilon 将取决于指数。例如,如果 mantissa 是 24 位,而 exponent 是 8 位有符号的,则对于某些值,1/(2^24)*2^127~2^103epsilon;或者这是指最小的epsilon
等一等。我说的是你的意思吗?您是在说为什么 |a-b|<epsilon 正确的。请将此链接添加到您的答案中;如果您同意cygnus-software.com/papers/comparingfloats/comparingfloats.htm,我可以删除我的愚蠢评论。
+1 这需要根据场景使用特殊的误差范围以及您在比较什么
这是一个很长的评论,本身不是答案。是否有适用于所有上下文的(一组)规范答案?
T
Trevor Hickey

与 epsilon 值进行比较是大多数人所做的(即使在游戏编程中也是如此)。

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

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

编辑:克里斯特在 recent blog post 上添加了有关此主题的大量重要信息。享受。


@DonReba:仅当 EPSILON 定义为 DBL_EPSILON 时。通常,它将是根据所需的比较精度选择的特定值。
EPSILON 当浮点数很大时比较不起作用,因为连续浮点数之间的差异也会变大。请参阅this article
难怪在一些游戏中当纹理/物体在远处闪烁时会出现 Z-fighting,例如在《战地风云 4》中。将差异与 EPSILON 进行比较几乎没有用处。您需要与对手头单位有意义的阈值进行比较。此外,请使用 std::abs,因为它针对不同的浮点类型进行了重载。
我投了反对票,因为示例代码显示了大多数程序员重复的典型错误。浮点总是关于相对误差,因为它是浮点(不是定点)。所以它永远不会在固定错误(epsilon)下正确工作。
@SirGuy,请发布答案以演示如何正确执行此操作,或链接到此处。我希望看到基于非 epsilon 的替代方案。
D
Damian

比较浮点数取决于上下文。由于即使更改操作顺序也会产生不同的结果,因此了解您希望数字有多“相等”很重要。

在查看浮点比较时,Bruce Dawson 的 Comparing floating point numbers 是一个很好的起点。

以下定义来自 The art of computer programming by 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);
}

当然,选择 epsilon 取决于上下文,并确定您希望数字有多相等。

比较浮点数的另一种方法是查看数字的 ULP(最后一个单位)。虽然没有专门处理比较,但论文 What every computer scientist should know about floating point numbers 是了解浮点如何工作以及存在哪些陷阱(包括 ULP 是什么)的一个很好的资源。


fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);救了我的命。大声笑请注意,这个版本(我没有检查是否也适用于其他版本)还考虑了浮点数的整数部分可能发生的变化(例如:2147352577.9999997616 == 2147352576.0000000000,您可以清楚地看到几乎两个数字之间相差 2),这非常好!当累积的舍入误差溢出数字的小数部分时,就会发生这种情况。
Bruce Dawson 的文章非常好,很有帮助,谢谢!
鉴于此问题被标记为 C++,您的检查将更容易阅读为 std::max(std::abs(a), std::abs(b))(或使用 std::min()); C++ 中的 std::abs 被 float & double 类型,所以它工作得很好(你总是可以保留 fabs 以提高可读性)。
definitelyGreaterThan 报告 true 肯定应该等于,即 not 大于。
原来问题出在我的代码中,原始预期值和解析字符串之间的差异。
B
Bo R

我发现 Google C++ Testing Framework 包含一个很好的基于模板的AlmostEqual2sComplement 跨平台实现,它适用于双精度数和浮点数。鉴于它是根据 BSD 许可证发布的,只要您保留许可证,在您自己的代码中使用它应该没有问题。我从 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 中提取了以下代码,并在顶部添加了许可证。

确保将#define GTEST_OS_WINDOWS 设置为某个值(或将其用于适合您的代码库的代码更改为适合您的代码库的代码——毕竟它是BSD 许可的)。

使用示例:

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

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

这是代码:

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

编辑:这篇文章已有 4 年历史。它可能仍然有效,并且代码很好,但有些人发现了改进。最好直接从 Google 测试源代码中获取最新版本的 AlmostEquals,而不是我在此处粘贴的那个。


+1:我同意这个是正确的。但是,它没有解释原因。见这里:cygnus-software.com/papers/comparingfloats/comparingfloats.htm我在这里写了关于最高分的评论后阅读了这篇博文;我相信它说了同样的话,并提供了上面实施的理性/解决方案。因为代码太多,人们会错过答案。
当隐式转换发生时,会发生一些令人讨厌的事情,比如 FloatPoint fp(0.03f)。我对此进行了一些修改以帮助防止这种情况。 template explicit FloatingPoint(const U& x) { if(typeid(U).name() != typeid(RawType).name()) { std::cerr << "你正在用浮点,不要" << std::endl;断言(typeid(U).name() == typeid(RawType).name()); } u_.value_ = x; }
好发现!我想最好将它们贡献给谷歌测试,不过,这段代码是从那里被盗的。我会更新帖子以反映可能有更新的版本。如果谷歌的人表现得发痒,你能把它放在例如 GitHub gist 中吗?那我也会链接到那个。
我是唯一一个通过比较双打 0 和 1e-16 得到“假”的人吗? 0 的有偏表示是 9223372036854775808 而 1e-16 的有偏表示是 13590969439990876604。这似乎是表示的不连续性,还是我做错了什么?
有关最新的代码片段,请参阅 herehere
g
grom

如需更深入的方法,请阅读 Comparing floating point numbers。这是该链接的代码片段:

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

maxUlps 的建议值是多少?
*(int*)&A;”会违反严格的别名规则吗?
根据 gtest(搜索 ULP),4 是可接受的数字。
以下是 Bruce Dawson 论文的一些更新(其中一篇链接在论文的简介中):randomascii.wordpress.com/2012/02/25/…randomascii.wordpress.com/2012/06/26/…
我花了一段时间才弄清楚 ULP 上的内容是:最后一个位置的单位
S
Shafik Yaghmour

意识到这是一个老话题,但这篇文章是我在比较浮点数时发现的最直接的文章之一,如果你想探索更多,它也有更详细的参考资料,它的主站点涵盖了一系列完整的问题处理浮点数 The Floating-Point Guide :Comparison

我们可以在 Floating-point tolerances revisited 中找到一篇更实用的文章,并指出有 absolute tolerance 测试,这在 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 ;
}

文章指出,当 xy 很大时,绝对测试失败,而当它们很小时,相对测试失败。假设绝对容差和相对容差相同,则组合测试将如下所示:

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

S
Shital Shah

我最终花了相当长的时间在这个伟大的线程中浏览材料。我怀疑每个人都想花这么多时间,所以我会强调我学到的东西和我实施的解决方案的总结。

快速总结

1e-8 与 1e-16 大致相同吗?如果您正在查看嘈杂的传感器数据,那么可能是的,但如果您正在进行分子模拟,那么可能不是!底线:您始终需要在特定函数调用的上下文中考虑容差值,而不仅仅是使其成为通用应用程序范围内的硬编码常量。对于通用库函数,具有默认容差的参数仍然很好。一个典型的选择是 numeric_limits::epsilon(),它与 float.h 中的 FLT_EPSILON 相同。然而,这是有问题的,因为比较像 1.0 这样的值的 epsilon 与比较像 1E9 这样的值的 epsilon 是不同的。 FLT_EPSILON 是为 1.0 定义的。检查数字是否在公差范围内的明显实现是 fabs(ab) <= epsilon 但这不起作用,因为默认 epsilon 是为 1.0 定义的。我们需要根据 a 和 b 来放大或缩小 epsilon。这个问题有两种解决方案:要么将 epsilon 设置为与 max(a,b) 成比例,要么可以在 a 周围获得下一个可表示的数字,然后查看 b 是否落在该范围内。前者称为“相对”方法,后者称为 ULP 方法。与 0 比较时,这两种方法实际上都失败了。在这种情况下,应用程序必须提供正确的容差。

实用功能实现 (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;
}

isDefinitelyLessThan 检查 diff < tolerance,这意味着 a 和 b 几乎相等(因此 a 不一定小于 b)。检查差异不是更有意义吗?两种情况下的容忍度?或者也许添加一个 orEqualTo 参数来控制近似相等检查是否应该返回 true。
对于小于和大于关系,我们需要使用 <>
C
Chris de Vries

在 C++ 中获取 epsilon 的可移植方法是

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

那么比较函数就变成了

#include <cmath>
#include <limits>

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

您最有可能需要该 epsilon 的倍数。
你不能只使用 std::abs 吗? AFAIK,std::abs 也为双打重载。如果我错了,请警告我。
@kolistivra,你错了。 'fabs' 中的 'f' 并不表示浮点类型。您可能正在考虑 C 函数 fabsf() 和 fabsl()。
实际上,由于浮点值变大,outlined in Bruce's article epsilon 会发生变化。请参阅他所说的部分 “对于大于 2.0 的数字,浮点数之间的差距会变得更大,如果您使用 FLT_EPSILON 比较浮点数,那么您只是在进行更昂贵且不太明显的相等性检查。”
我知道这很旧,但 std::abs 对于 cmath 中的浮点类型来说是重载的。
f
fulmicoton

你写的代码有问题:

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

正确的代码是:

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

(......是的,这是不同的)

我想知道在某些情况下晶圆厂是否不会让你失去懒惰的评估。我会说这取决于编译器。您可能想尝试两者。如果它们平均相等,请使用 fabs 实施。

如果您有一些关于两个浮点数中的哪一个更可能比另一个更大的信息,您可以按照比较的顺序进行操作,以更好地利用惰性评估。

最后,通过内联这个函数,你可能会得到更好的结果。虽然不太可能改善...

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


该问题已被编辑为现在正确。 return (diff < EPSILON) && (diff > -EPSILON);return (diff < EPSILON) && (-diff < EPSILON); 都是等价的并且都是正确的。
佚名

`return fabs(a - b) < EPSILON;

这很好,如果:

您输入的数量级变化不大

非常少量的相反符号可以被视为相等

但否则它会给你带来麻烦。双精度数字的分辨率约为小数点后 16 位。如果您要比较的两个数字的幅度大于 EPSILON*1.0E16,那么您不妨说:

return a==b;

我将研究另一种方法,假设您需要担心第一个问题并假设第二个问题对您的应用程序来说很好。解决方案类似于:

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

这在计算上是昂贵的,但有时是需要的。这是我们在我公司必须做的,因为我们处理的是一个工程库,输入可能会有几十个数量级的变化。

无论如何,重点是这一点(并且几乎适用于所有编程问题):评估您的需求,然后提出解决方案来满足您的需求——不要假设简单的答案就能满足您的需求。如果在您的评估之后您发现 fabs(a-b) < EPSILON 就足够了,那么完美 - 使用它!但也要注意它的缺点和其他可能的解决方案。


除了拼写错误(s/-/,/ fmax() 中缺少逗号)之外,此实现对于 EPSILON 中接近零的数字有一个错误,但还不是非常小。例如,AreSame(1.0E-10, 1.0E-9) 报告错误,因为相对误差很大。你将成为你公司的英雄。
@brlcad您没有得到浮点数。 1.0E-10 和 1.0E-9 相差 10 的量级。所以它们确实不一样。浮点总是关于相对误差。如果您有一个系统认为 1.0E-10 和 1.0E-9 几乎相等,因为两者都“非常接近于零”(这对人类来说听起来很合理,但在数学上毫无意义),那么 EPSILON 需要适当调整对于这样的系统。
S
Steve Hollasch

正如其他人指出的那样,对于远离 epsilon 值的值,使用固定指数 epsilon(例如 0.0000001)将毫无用处。例如,如果您的两个值是 10000.000977 和 10000,那么这两个数字之间没有 32 位浮点值 - 10000 和 10000.000977 在不逐位相同的情况下尽可能接近。在这里,小于 0.0009 的 epsilon 是没有意义的;您不妨使用直接相等运算符。

同样,当这两个值的大小接近 epsilon 时,相对误差会增长到 100%。

因此,尝试将诸如 0.00001 之类的定点数与浮点值(其中指数是任意的)混合是毫无意义的。只有当您可以确保操作数值位于一个狭窄的域内(即接近某个特定指数)并且您为该特定测试正确选择了一个 epsilon 值时,这才会起作用。如果你从空中拉出一个数字(“嘿!0.00001 很小,所以那一定很好!”),你注定会出现数字错误。我花了很多时间调试糟糕的数字代码,其中一些糟糕的 schmuck 会在随机 epsilon 值中折腾以使另一个测试用例工作。

如果您进行任何类型的数值编程并认为需要使用定点 epsilon,请阅读 BRUCE 关于比较浮点数的文章。

Comparing Floating Point Numbers


S
Steve Hollasch

这是使用 std::numeric_limits::epsilon() 不是答案的证据——它对于大于 1 的值会失败:

我上面评论的证明:

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

运行产生以下输出:

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

请注意,在第二种情况下(一且仅大于一),两个输入值尽可能接近,但仍比较不接近。因此,对于大于 1.0 的值,您不妨只使用相等测试。比较浮点值时,固定的 epsilon 不会为您节省时间。


我相信 return *(reinterpret_cast<double*>(&x)); 虽然它通常有效,但实际上是未定义的行为。
公平点,尽管此代码是说明性的,但足以证明 numeric_limits<>::epsilon 和 IEEE 754 地板点的问题。
也是一个公平的观点,但在堆栈溢出上发布期望这种洞察力是不明智的。代码将被盲目复制,使得根除这种非常常见的模式变得更加困难——连同联合技巧——应该像所有 UD 一样避免。
D
Dana Yan

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

您可能需要以下功能,因为

请注意,比较 p1 或 p2 为 0.0 的值将不起作用,比较其中一个值为 NaN 或无穷大的值也不起作用。如果其中一个值始终为 0.0,请改用 qFuzzyIsNull。如果其中一个值可能为 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;
}

D
Don Reba

不幸的是,即使您的“浪费”代码也不正确。 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)))
{
    // ...
}

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


“EPSILON 是可以添加到 1.0 并更改其值的最小值”:实际上,这个荣誉属于 0.5*EPSILON 的后继者(在默认的四舍五入模式下)。 blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
为什么您认为问题中的 EPSILONDBL_EPSILONFLT_EPSILON?问题出在您自己的想象中,您将 DBL_EPSILON(这确实是错误的选择)替换为没有使用它的代码。
@BenVoigt,你是对的,当时我在想这件事,我从这个角度解释了这个问题。
佚名

浮点数的通用比较通常没有意义。如何比较实际上取决于手头的问题。在许多问题中,数字被充分离散化以允许在给定的容差范围内进行比较。不幸的是,同样有很多问题,这种技巧并没有真正奏效。例如,当您的观察非常接近障碍时,请考虑使用相关数字的 Heaviside(步进)函数(想到数字股票期权)。执行基于容差的比较不会有多大好处,因为它会有效地将问题从原来的障碍转移到两个新的障碍。同样,此类问题没有通用解决方案,特定解决方案可能需要更改数值方法以实现稳定性。


G
Gabriel Staples

您必须对浮点比较进行此处理,因为浮点数不能像整数类型那样完美地比较。以下是各种比较运算符的函数。

浮点等于 (==)

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

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

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

示例用法:

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

我不完全确定,但在我看来,对基于 epsilon 的方法的一些批评,如 this highly-upvoted answer 下面的评论中所述,可以通过使用变量 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;
}

其他改进:

C++ 中 epsilon 的一个好的默认值是 std::numeric_limits::epsilon(),其计算结果为 0 或 FLT_EPSILON、DBL_EPSILON 或 LDBL_EPSILON。请参阅此处:https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon。您还可以查看 FLT_EPSILON、DBL_EPSILON 和 LDBL_EPSILON 的 float.h 标头。请参阅 https://en.cppreference.com/w/cpp/header/cfloat 和 https://www.cplusplus.com/reference/cfloat/ 您可以改为模板函数,以处理所有浮点类型:float、double , 和 long double,通过模板内的 static_assert() 对这些类型进行类型检查。缩放 epsilon 值是一个好主意,以确保它适用于非常大和非常小的 a 和 b 值。本文推荐并解释:http://realtimecollisiondetection.net/blog/?p=89。因此,您应该通过等于 max(1.0, abs(a), abs(b)) 的缩放值来缩放 epsilon,正如那篇文章所解释的那样。否则,随着 a 和/或 b 幅度的增加,epsilon 最终将相对于那些值变得非常小,以至于它会丢失在浮点误差中。因此,我们将其缩放为像它们一样在数量级上变得更大。但是,使用 1.0 作为 epsilon 的最小允许比例因子还可以确保对于非常小的 a 和 b 值,epsilon 本身不会被缩放得太小,以至于它也会丢失在浮点错误中。因此,我们将最小比例因子限制为 1.0。如果您想将上述函数“封装”到一个类中,请不要。相反,如果您愿意,可以将它们包装在命名空间中以便命名它们。例如:如果将所有独立函数放入名为 float_comparison 的命名空间中,则可以像这样访问 is_eq() 函数,例如:float_comparison::is_eq(1.0, 1.5);。添加与零的比较也可能很好,而不仅仅是两个值之间的比较。因此,这里有一种更好的解决方案,其中包含上述改进: namespace float_comparison { /// 根据上面的解释,将 epsilon 值缩放为较大的 a 或 b, /// 但不小于 1.0 , 以确保 /// epsilon 永远不会因为 a 和/或 b /// 幅度的增加而出现浮点错误。模板 static constexpr T scale_epsilon(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept { static_assert(std::is_floating_point_v, "浮点比较" "需要类型 float、double 或 long double。"); T 缩放因子; // a 或 b 为无穷大时的特殊情况 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);返回 epsilon_scaled; } // 比较两个值 /// 相等:如果 a 近似 == b,则返回 true,否则返回 false template static constexpr bool is_eq(T a, T b, T epsilon = std::numeric_limits ::epsilon()) noexcept { static_assert(std::is_floating_point_v, "浮点比较" "需要类型 float、double 或 long double。"); // 首先测试 `a == b` 以查看 a 和 b 是否都是无穷大 // 或 -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() */ // 与零比较 /// 相等:如果 a 近似 == 0,则返回 true,否则返回 false否则 template static constexpr bool is_eq_zero(T a, T epsilon = std::numeric_limits::epsilon()) noexcept { static_assert(std::is_floating_point_v, "浮点比较" "需要类型float、double 或 long double。");返回 is_eq(a, (T)0.0, epsilon); } /* 等等等等: is_eq_zero() is_ne_zero() is_lt_zero() is_le_zero() is_gt_zero() is_ge_zero() */ } // 命名空间 float_comparison

也可以看看:

上面的一些函数的宏形式在我的仓库中:utilities.h。 2020 年 11 月 29 日更新:这是一项正在进行中的工作,准备好后我将把它作为一个单独的答案,但我已经在此文件中为 C 中的所有函数生成了一个更好的缩放 epsilon 版本这里: 实用程序.c。看一看。我现在需要做的补充阅读:Christer Ericson 重新审视了浮点容差。非常有用的文章!它讨论了缩放 epsilon 以确保它永远不会陷入浮点错误,即使对于非常大的 a 和/或 b 值也是如此!


W
WaterbugDesign

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

此代码旨在演示该概念,而不是通用解决方案。 Google 的代码已经展示了如何计算所有平台特定的值,我不想重复所有这些。我已经对此代码进行了有限的测试。

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

B
Boojum

我会非常警惕任何涉及浮点减法的答案(例如,fabs(ab) < epsilon)。首先,浮点数在更大的幅度和足够高的幅度(间距大于ε)处变得更加稀疏,您不妨只做a == b。其次,减去两个非常接近的浮点数(因为这些往往是,因为您正在寻找近似相等)正是您获得 catastrophic cancellation 的方式。

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


+1 以获得良好的信息。但是,我看不出如何通过增加相对误差来搞乱相等比较;恕我直言,错误仅在减法的结果中变得显着,但是它相对于被减去的两个操作数的数量级应该仍然足够可靠以判断是否相等。除非总体上需要更高的分辨率,但在这种情况下,唯一的解决方案是移动到尾数中具有更多有效位的浮点表示。
减去两个几乎相等的数字不会导致灾难性的抵消——事实上,它根本不会引入任何错误(qv Sterbenz 定理)。在计算 ab 本身的过程中,灾难性取消发生得更早。使用浮点减法作为模糊比较的一部分绝对没有问题(尽管正如其他人所说,绝对 epsilon 值可能适合也可能不适合给定的用例。)
C
Community

在数值软件中实际上存在您想要检查两个浮点数是否完全相等的情况。我在一个类似的问题上发布了这个

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

所以你不能说“CompareDoubles1”通常是错误的。


实际上是一个很好的答案的非常可靠的参考,尽管它非常专业地限制没有科学计算或数值分析背景的人(即 LAPACK,BLAS)不了解完整性。或者换句话说,它假定您已经阅读了 Burden & 的 Numerical Recipes 介绍或 Numerical Analysis 之类的内容。费尔斯。
T
Tomilov Anatoliy

从数量规模来看:

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

C
Chunde Huang

我使用这段代码:

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

这不是 epsilon 的用途。
为什么不?你能解释一下吗?
@debuti epsilon 只是 1 和 1 之后的下一个可表示数字之间的距离。充其量,该代码只是试图检查这两个数字是否完全彼此相等,但因为非- 2 的幂乘以 epsilon,它甚至没有正确地做到这一点。
哦,std::fabs(std::min(v1, v2)) 是不正确的——对于负输入,它会选择幅度较大的输入。
P
Prashant Nidgunde

发现另一个有趣的实现: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";
}

A
André Sousa

以更通用的方式:

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

注意:正如@SirGuy 所指出的,这种方法是有缺陷的。我把这个答案留在这里作为一个例子,不要效仿。


这种方法有很多弱点,比如如果数字 ab 已经小于 epsilon(),那么差异可能仍然很大。相反,如果数字非常大,那么即使您确实希望数字被视为相等,即使是几位错误也会使比较失败。这个答案正是您要避免的“通用”比较算法类型。
@SirGuy 这与回答 3 个帖子并获得 100 多个支持的答案有何不同?
@algae,如果您问我为什么在此答案上发表此评论,而不是与 100+ 赞成票相同的评论,那么我没有注意到原因。
C
Carlo Wood

我使用此代码。与上述答案不同,这允许人们给出代码注释中解释的 abs_relative_error

第一个版本比较复数,因此可以用复平面中相同长度的两个“矢量”之间的角度来解释错误(这提供了一点见解)。然后从那里得出两个实数的正确公式。

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

那么后者是

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

其中 abs_relative_error 基本上是(两倍)最接近文献中定义的绝对值:相对误差。但这只是名称的选择。

我认为在复平面中最清楚地看到了它的真正含义。如果 |x| = 1,并且 y 位于直径为 abs_relative_error 的 x 周围,则认为两者相等。


T
Tim Cooper

这取决于您希望比较的精确程度。如果您想比较完全相同的数字,那么只需使用 ==。 (除非您实际上想要完全相同的数字,否则您几乎永远不想这样做。)在任何体面的平台上,您还可以执行以下操作:

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

因为 fabs 往往很快。相当快我的意思是它基本上是一个按位与,所以最好是快。

比较双精度和浮点数的整数技巧很好,但往往会使各种 CPU 管道更难以有效处理。由于使用堆栈作为经常使用的值的临时存储区域,现在在某些有序架构上肯定不会更快。 (为那些关心的人加载命中商店。)


M
Murphy78
/// 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 测试它,这次我得到了预期的结果。


D
Daniel Laügt

您不能将两个 double 与固定的 EPSILON 进行比较。根据 double 的值,EPSILON 会有所不同。

更好的双重比较是:

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

V
Vijay

我的方法可能不正确但很有用

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

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;

}

运算符重载也可以


+1:嘿,我不打算用这个来做游戏编程,但是在布鲁斯道森的博客(论文?:D)中多次提出往返浮动的想法,如果你被困在一个房间,有人用枪指着你的头说:“嘿,你必须比较两个浮点数在 X 有效数字内,你有 5 分钟,开始!”这可能是一个需要考虑的问题。 ;)
@shelleybutterfly 然后问题是比较两个浮点数的最有效方法。
@TommyA 大声笑,但我敢打赌,由于与效率无关的原因,往返票被否决了。虽然我的直觉是它与硬件 fp 数学相比效率相当低,但也表示软件 fp 中的算法至少不太可能有很大的差异。我热切地等待您所做的分析,表明在这种情况下效率问题很重要。此外,有时不太理想的答案仍然是一个有价值的答案,并且因为它被否决了——尽管它是一种有效的技术,甚至在 Dawson 的博客中就该主题提到过,所以我认为它值得一票。
C
Chameleon

我是为 java 写的,但也许你觉得它很有用。它使用 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; }

请记住,经过多次浮点运算后,数字可能与我们预期的大不相同。没有代码可以解决这个问题。


A
Amir Saniyan

这是 lambda 的另一种解决方案:

#include <cmath>
#include <limits>

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

这与许多其他答案完全相同,只是它是一个 lambda 并且没有解释,所以这并没有作为答案增加太多价值。