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


524

比较两个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);
}

似乎是废物处理。

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


2
>在函数的开头添加...会更有效吗?<invoke Knuth>过早的优化是万恶之源。</invoke Knuth>如上所述,只要使用abs(ab)<EPS,就很容易理解。
安德鲁·科尔森

2
这是在Boost测试库中实现的方法:http
Alessandro Jacopson

2
原始海报实现唯一不理想的是,它在&&处包含一个额外的分支。OJ的答案是最佳的。fabs是一个内在函数,它是x87上的一条指令,我想几乎也可以使用其他指令。已经接受OJ的答案!
2010年

3
如果可以,请删除浮点并使用固定点。例如,使用{固定点}毫米而不是{浮点}米。
Thomas Matthews 2012年

33
“仅仅这样做是不正确的” -这仅仅是垃圾,使用当然==可以完全正确,但这完全取决于问题中未给出的上下文。在知道该上下文之前,==仍然保持“最有效的方式”
克里斯汀·劳

Answers:


459

使用其他建议时要格外小心。这完全取决于上下文。

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

  1. if a==band b==cthen 的算法中的隐式推定a==c

  2. 对于以英寸为单位的线和以密耳(.001英寸)为单位的线,请使用相同的epsilon。那a==b不过是1000a!=1000b。(这就是为什么AlmostEqual2sComplement要求epsilon或最大ULPS的原因)。

  3. 角度的余弦和线的长度都使用相同的epsilon!

  4. 使用这种比较功能对集合中的项目进行排序。(在这种情况下,使用内置的C ++运算符==可以使双精度产生正确的结果。)

就像我说:这一切都取决于背景和预期的大小ab

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

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


10
+1指出显而易见的(通常会被忽略)。对于通用方法,您可以相对于epsilon,fabs(a)+fabs(b)但要补偿NaN,0和和溢出,这会变得相当复杂。
peterchen 2010年

4
一定有些我不懂的东西。典型的float/ doubleMANTISSA x 2 ^ EXPepsilon将取决于指数。例如,如果尾数是24位并且指数是有符号的8位,然后1/(2^24)*2^127或者~2^103是一个epsilon用于一些值; 还是指最小的ε
无声的噪音

3
等一下。我说的是你的意思吗?您是在说为什么|a-b|<epsilon正确。请将此链接添加到您的答案中;如果您同意cygnus-software.com/papers/comparingfloats/comparingfloats.htm,我可以删除我的愚蠢评论。
无声的噪音

3
这是一个很长的评论,本身并不是一个答案。是否有针对所有上下文的(一组)规范答案?
Merlyn Morgan-Graham

2
旧的链接似乎已经过时,新的页面在这里randomascii.wordpress.com/2012/02/25/...
马森茂

174

与大多数人所做的事情(包括在游戏编程中)相比,与ε值进行比较。

您应该对实现进行一些更改:

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

编辑:克里斯特(Christer)在最近的博客文章中添加了有关此主题的大量信息。请享用。


@OJ:第一个代码示例有问题吗?我认为唯一的问题是在这样的情况下:float a = 3.4; if(a == 3.4){...}即,当您将存储的浮点数与文字| 在这种情况下,两个数字都存储了,因此,如果相等,它们将具有相同的表示形式,那么这样做的危害是什么a == b
Lazer

11
@DonReba:仅当EPSILON定义为时DBL_EPSILON。通常,它将是一个特定值,具体取决于所需的比较精度。
Nemo157 2011年

7
EPSILON浮点较大时,比较不起作用,因为连续的浮点之间的差异也会变大。看到这篇文章
kevintodisco

22
难怪在某些游戏中,当远处的纹理/对象闪烁时会发生Z角战斗,例如在《战地风云4》中进行比较EPSILON。您需要与对手边的单位有意义的阈值进行比较。另外,std::abs由于不同的浮点类型会重载它,因此请使用。
Maxim Egorushkin 2014年

11
由于示例代码显示了大多数程序员重复的典型错误,因此我对此表示不满。浮点数始终是相对误差,因为它是浮点数(不是固定点)。因此,它将永远无法正确地解决固定错误(ε)。
user2261015 '17

115

我发现Google C ++测试框架包含一个很好的基于跨平台模板的AlmostEqual2sComplement实现,该实现可用于double和float。鉴于它是根据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并在顶部添加了许可证。

确保将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岁。它可能仍然有效,并且代码很好,但是有些人发现了改进。最好AlmostEquals从Google Test源代码获得正确的最新版本,而不是从我这里粘贴的版本。


3
+1:我同意这是正确的。但是,它没有解释原因。参见此处:cygnus-software.com/papers/comparingfloats/comparingfloats.htm 在对最高分发表评论后,我阅读了此博客文章。我相信它说的是同一件事,并提供了上面实现的合理/解决方案。因为代码太多,所以人们会错过答案。
无声的噪音

当隐式强制转换(例如FloatPoint <double> fp(0.03f))发生时,可能会发生一些令人讨厌的事情。我对此做了一些修改,以帮助防止这种情况。template <typename U>显式FloatingPoint(const U&x){if(typeid(U).name()!= typeid(RawType).name()){std :: cerr <<“您正在使用FloatingPoint,不要“ << std :: endl; assert(typeid(U).name()== typeid(RawType).name()); } u_.value_ = x; }
JeffCharter 2014年

2
好发现!我想最好将它们贡献给Google Test,因为代码是从那里窃取的。我将更新该帖子以反映可能有一个较新的版本。如果Google家伙发痒,您能否将其放入GitHub要点中?然后,我也将链接到该链接。
skrebbel

3
有关最新的代码段,请参见此处此处
Jaege

1
我已经将必要的行提取到要点文件中。任何人都可以从这里到达。
YusufTarıkGünaydın17年

111

比较浮点数是否取决于上下文。由于即使更改操作顺序也会产生不同的结果,因此重要的是要知道数字的期望值“相等”。

在查看浮点比较时,Bruce Dawson 比较浮点数是一个不错的起点。

以下定义来自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(最后一个单位)。虽然没有专门讨论比较问题,但是每位计算机科学家都应该了解的有关浮点数的文章是了解浮点如何工作以及陷阱(包括ULP是什么)的好资源。


1
感谢您发布如何确定哪个数字更大/更大!
西红柿

1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);救了我的命。大声笑请注意,此版本(我也没有检查过是否也适用于其他版本)也考虑了可能会在浮点数的整数部分中发生的更改(例如:2147352577.9999997616 == 2147352576.0000000000您可以清楚地看到其中的差值2两个数字之间),这非常不错!当累积的舍入误差溢出数字的小数部分时,会发生这种情况。
rbaleksandar '16

布鲁斯·道森(Bruce Dawson)的这篇非常有用的文章,谢谢!
BobMorane

2
假设这个问题被标记为C ++,那么将您的检查写成std::max(std::abs(a), std::abs(b))(或使用std::min())会更容易阅读;std::abs在C ++中,float和double类型重载了,所以它工作得很好(fabs尽管您始终可以保持可读性)。
拉扎克尔

1
原来问题出在我的代码中,原始期望值和已解析的字符串之间存在差异。
mwpowellhtx

47

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

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

14
maxUlps的建议值是多少?
unj2 2011年

6
*(int*)&A;违反严格的别名规则吗?
osgx 2011年

3
根据gtest(搜索ULP),4是可接受的数字。
May Oakes 2012年

4
这里有一对夫妇的更新布鲁斯·道森的文件(其中一个在本文的介绍链接):randomascii.wordpress.com/2012/02/25/...randomascii.wordpress.com/2012/06/26/...
迈克尔·伯(Michael Burr)2012年

3
我花了一段时间才弄清楚ULP上的内容是:最后的单位
JeffCharter 2014年

27

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

我们可以在浮点公差中找到一些更实用的文章,并注意到存在绝对公差测试,这可以归结为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 ;
}

25

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

34
您将很可能需要该epsilon的倍数。
user7116

11
您不能只使用std :: abs吗?AFAIK,std :: abs也重载为双精度。如果我错了,请警告我。
kolistivra

3
@kolistivra,您错了。'fabs'中的'f'并不意味着float类型。您可能正在考虑C函数fabsf()和fabsl()。
jcoffland 2012年

9
实际上,由于Bruce文章中概述 的原因ε随浮点值变大而变化。请参阅他说的部分:“对于大于2.0的数字,浮点数之间的差距会变大,如果使用FLT_EPSILON比较浮点数,那么您只是在进行更昂贵且不太明显的相等性检查。”
bobobobo

5
我知道这是旧的,但对于cmath中的浮点类型,std :: abs重载。
mholzmann 2013年

18

我最终花了相当多的时间来研究这个伟大的话题。我怀疑每个人都想花费那么多时间,以便我重点介绍我所学到的知识和所实施的解决方案的摘要。

快速总结

  1. 1e-8与1e-16大致相同吗?如果您正在查看嘈杂的传感器数据,则可能是,但是如果您正在进行分子模拟,则可能不是!底线:您始终需要在特定函数调用的上下文中考虑公差值,而不仅仅是使其成为通用的应用程序范围内的硬编码常量。
  2. 对于一般的库函数,最好使用具有默认公差的参数。一个典型的选择是numeric_limits::epsilon()与float.h中的FLT_EPSILON相同。但是,这是有问题的,因为用于比较1.0之类的值的epsilon与用于1E9之类的值的epsilon不相同。FLT_EPSILON为1.0定义。
  3. 但是,检查数字是否在公差范围内的显而易见的实现方法fabs(a-b) <= epsilon不起作用,因为默认的epsilon定义为1.0。我们需要按a和b放大或缩小ε。
  4. 解决此问题的方法有两种:要么将epsilon设置为正比,max(a,b)要么可以得到a附近的下一个可表示数字,然后查看b是否落入该范围内。前者称为“相对”方法,后者称为ULP方法。
  5. 与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;
}

isDefinitelyLessThanchecks diff < tolerance,这意味着a和b几乎相等(因此a肯定不小于b)。在这两种情况下检查diff>公差不是更有意义吗?或添加一个orEqualTo参数来控制近似相等性检查是否应返回true。
Matt Chambers

14

您编写的代码存在错误:

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

正确的代码是:

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

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

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

如果您了解两个浮点数中哪个比另一个浮点数更大的信息,则可以按比较的顺序进行操作,以更好地利用延迟评估。

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

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


13

`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满足要求,那就完美了-使用它!但是也要意识到它的缺点和其他可能的解决方案。


3
除了错别字(fmax()中的s /-/,/缺少逗号)之外,此实现还存在一个错误,即EPSILON内的数字接近零,但还不太完全。例如,AreSame(1.0E-10,1.0E-9)报告错误,因为相对误差很大。您将成为公司的英雄。
brlcad 2010年

1
@brlcad您没有得到浮点数。1.0E-10和1.0E-9的差异为10。因此,它们确实是不同的。浮点数总是与相对误差有关。如果您有一个系统将1.0E-10和1.0E-9视为几乎相等,因为它们都“非常接近于零”(对人类来说这听起来很合理,但从数学上来说并没有什么意义),则需要适当调整EPSILON对于这样的系统。
user2261015 2015年

8

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

同样,当两个值的大小接近ε时,相对误差将增加到100%。

因此,尝试将诸如0.00001之类的定点数与浮点值(指数是任意的)混合使用是没有意义的。仅在可以确保操作数值位于狭窄域内(即接近某个特定指数)并且为该特定测试正确选择了ε值时,此方法才有用。如果您突然想出一个数字(“嘿!0.00001很小,那一定很好!”),那么注定会出现数字错误。我花了很多时间来调试错误的数字代码,其中一些差劲的schmuck会以随机的epsilon值抛出来使另一个测试用例起作用。

如果您进行任何形式的数值编程,并且认为需要达到定点的ε,请阅读BRUCE的比较浮点数的文章

比较浮点数


5

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

3

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


3

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

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


2
“ EPSILON是可以添加到1.0并更改其值的最小值”:实际上,该荣誉授予了0.5 * EPSILON的后继者(在默认的四舍五入模式下)。blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq

您为什么认为EPSILON问题是DBL_EPSILONFLT_EPSILON?问题出在您自己的想象中,您将DBL_EPSILON(实际上是错误的选择)替换为不使用它的代码。
Ben Voigt 2015年

@BenVoigt,你是对的,那是我当时的想法,我以此来解释这个问题。
唐·雷巴

2

我的课程基于先前发布的答案。与Google的代码非常相似,但是我使用了一个偏向,将所有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 );
                  }
};

2

这证明使用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的值,您最好只使用相等性测试。比较浮点值时,固定的epsilons不会节省您的时间。


我相信return *(reinterpret_cast<double*>(&x));尽管它通常可以工作,但实际上是不确定的行为。
Jaap Versteegh,

公平的观点,尽管此代码是说明性的,但足以证明该问题numeric_limits<>::epsilon和IEEE 754地板问题。
史蒂夫·霍拉斯

这也是一个公平的观点,但是恕我直言地希望在堆栈溢出时发布此类期望是不明智的。该代码被盲目复制,从而使消除这种非常常见的模式(以及联合技巧)变得更加困难,因为所有UD都应避免这种情况。
Jaap Versteegh

1

在以下位置找到了另一个有趣的实现: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";
}

0

对于任何涉及浮点减法的答案,我都会非常警惕(例如,fabs(ab)<epsilon)。首先,浮点数在较大的幅度和足够高的幅度(间隔大于epsilon)处变得更稀疏,您最好也执行a == b。其次,减去两个非常接近的浮点数(鉴于您正在寻找近似相等的情况,这很容易实现)灾难性抵消的方式

虽然不是便携式的,但我认为grom的答案在避免这些问题方面做得最好。


1
+1获取良好的信息。但是,我看不到如何通过增加相对误差来弄乱相等比较。恕我直言,该错误仅在相减的结果中才变得显着,但是,相对于被减去的两个操作数而言,其数量级仍应足够可靠,足以判断是否相等。除非分辨率总体上需要更高,否则在这种情况下,唯一的解决方案是移动到尾数中具有更高有效位的浮点表示形式。
sehe 2011年

减去两个几乎相等的数字不会导致灾难性的抵消-实际上,它根本不会引入任何误差(qv斯特本兹定理)。在ab自身的计算过程中,灾难性取消发生得较早。使用浮点减法作为模糊比较的一部分绝对没有问题(尽管正如其他人所说,绝对epsilon值可能适合或可能不适合给定的用例。)
Sneftel

0

实际上,在数字软件中,您需要检查两个浮点数是否完全相等。我在类似的问题上发布了

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

因此,您不能说“ CompareDoubles1”通常是错误的。


实际上,它是对一个好的答案的非常扎实的参考,尽管它非常专门用于限制没有科学计算或数值分析背景的任何人(例如LAPACK,BLAS)都不了解完整性。换句话说,它假设您已经阅读了《数字食谱》或《Burden&Faires的数值分析》等内容。
mctylr

0

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

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

fabs往往很快。很快,我的意思是基本上是按位与的,所以最好快。

用于比较双精度和浮点数的整数技巧很不错,但往往使各种CPU管道更难以有效处理。而且,由于将堆栈用作经常使用的值的临时存储区域,因此在当今某些有序体系结构中,这绝对不会更快。(专为那些关心的人而设。)


0

就数量规模而言:

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

0

我使用以下代码:

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

2
那不是epsilon为了什么
Sneftel

1
为什么不?你能解释一下吗?
首映

2
@debuti epsilon只是1与1之后的下一个可表示数字之间的距离。充其量,该代码只是试图检查两个数字是否彼此完全相等,但是由于将2的非幂乘以epsilon,所以它甚至没有正确地做到这一点。
Sneftel

2
哦,这std::fabs(std::min(v1, v2))是不对的-对于负输入,它将选择幅度较大的输入。
Sneftel

0

我为Java编写了此代码,但也许您发现它很有用。它使用long而不是double,但是会处理NaN,次正规等。

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

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


0

这个怎么样?

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

我见过各种方法-但从未见过,所以我也很想听到任何评论!


这不适用于1.99999999和1.99999998
Mehdi

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


-1

这是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且没有解释,因此作为答案并没有增加太多价值。
stijn

-2

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

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

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:嘿,我不会为此做游戏编程,但是在这个问题上,布鲁斯·道森(Bruce Dawson)的博客(treatise?:D)多次提出了往返浮动的想法,如果您被困在其中一个房间,有人把枪对准你的头说:“嘿,你必须比较两个浮子到X个有效数字之内,你有5分钟,GO!” 这可能是要考虑的一个。;)
shelleybutterfly 2014年

@shelleybutterfly然后,问题再次是比较两个浮点数的最有效方法。
汤米·安德森

@TommyA大声笑,但我打赌来回程票因与效率无关的原因而被否决。尽管我的直觉是,与HW fp数学相比,它的效率相当低,但他还说,软件fp中的算法至少不太可能有很大的不同。我热切期待您所做的分析表明在那种情况下效率问题非常重要。此外,有时低于最佳的答案仍然是一个有价值的答案,而且它被否决了,尽管这是一种有效的技术,甚至在道森的博客中也提到了这一点,所以我认为这值得一提。
shelleybutterfly 2014年

-2

您不能将两者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;
}

-2

以更通用的方式:

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

4
此方法有许多缺点,例如,如果ab已经小于该数量,则epsilon()差异可能仍然很大。相反,如果数字很大,那么即使您确实希望将数字视为相等,即使是几位错误也会使比较失败。该答案正是您要避免的“通用”比较算法的类型。
SirGuy

-3

为什么不执行按位异或?如果两个浮点数的对应位相等,则它们相等。我认为,决定将指数位放在尾数之前是为了加快两个浮点数的比较。我认为,这里的许多答案都缺少epsilon比较的意义。Epsilon值仅取决于要比较的精度浮点数。例如,对浮点数进行了一些算术运算后,您得到两个数字:2.5642943554342和2.5642943554345。它们不相等,但是对于解决方案,只有3个十进制数字很重要,因此它们相等:2.564和2.564。在这种情况下,您选择的ε等于0.001。Epsilon比较也可以通过按位XOR进行。如果我错了,请纠正我。


请不要对多个问题添加相同的答案。回答最好的一个,并将其余的标记为重复。见meta.stackexchange.com/questions/104227/...
Bhargav饶

我不认为仅使用ExOr(和一两个比较)就可能进行“ε比较”,甚至不限于使用相同格式的标准化表示形式。
greybeard
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.