提问人:Alex 提问时间:8/20/2008 最后编辑:Jan SchultkeAlex 更新时间:9/18/2023 访问量:637137
在考虑精度损失的同时,如何比较浮点和双倍?
How do you compare float and double while accounting for precision loss?
问:
比较两个或两个值的最有效方法是什么?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);
}
似乎浪费了处理。
有谁知道更智能的浮子比较器?
答:
与 epsilon 值的比较是大多数人所做的(即使在游戏编程中也是如此)。
不过,您应该稍微更改一下您的实现:
bool AreSame(double a, double b)
{
return fabs(a - b) < EPSILON;
}
编辑:克里斯特在最近的一篇博客文章中添加了一堆关于这个主题的重要信息。享受。
评论
EPSILON
DBL_EPSILON
EPSILON
std::abs
您编写的代码被窃听了:
return (diff < EPSILON) && (-diff > EPSILON);
正确的代码是:
return (diff < EPSILON) && (diff > -EPSILON);
(...是的,这是不同的)
我想知道在某些情况下,晶圆厂是否不会让你失去懒惰的评估。我会说这取决于编译器。您可能想同时尝试这两种方法。如果它们在平均值上是等效的,则采用晶圆厂的实施。
如果你有一些关于两个浮点数中哪一个比另一个浮点数更大的信息,你可以按照比较的顺序来更好地利用惰性评估。
最后,通过内联此函数可能会获得更好的结果。不过不太可能有太大改善......
编辑:OJ,感谢您更正您的代码。我相应地删除了我的评论
评论
return (diff < EPSILON) && (diff > -EPSILON);
return (diff < EPSILON) && (-diff < EPSILON);
有关更深入的方法,请阅读比较浮点数。以下是该链接中的代码片段:
// 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;
}
评论
*(int*)&A;
这取决于您希望比较的精确程度。如果您想比较完全相同的数字,则只需使用 ==。(你几乎从来不想这样做,除非你真的想要完全相同的数字。在任何体面的平台上,您还可以执行以下操作:
diff= a - b; return fabs(diff)<EPSILON;
因为往往非常快。我的意思是它基本上是按位 AND,所以最好快。fabs
用于比较双精度值和浮点数的整数技巧很好,但往往会使各种 CPU 管道更难有效处理。如今,由于将堆栈用作频繁使用的值的临时存储区域,因此在某些有序架构上,它绝对不会更快。(Load-hit-store为那些关心的人。
浮点数的通用比较通常没有意义。如何比较实际上取决于手头的问题。在许多问题中,数字被充分离散化,以便在给定的容差范围内进行比较。不幸的是,也有很多问题,这种技巧并没有真正起作用。例如,当您的观察结果非常接近障碍时,请考虑使用相关数字(数字股票期权)的 Heaviside(步进)函数。执行基于公差的比较不会有太大好处,因为它会有效地将问题从原始障碍转移到两个新障碍。同样,对于此类问题,没有通用的解决方案,特定的解决方案可能需要改变数值方法以实现稳定性。
'返回晶圆厂(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
评论
在 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();
}
评论
使用任何其他建议时要格外小心。这完全取决于上下文。
我花了很长时间跟踪系统中的错误,该系统假定如果.根本问题是:a==b
|a-b|<epsilon
算法中的隐式假设,即 if 和 then .
a==b
b==c
a==c
对以英寸为单位测量的线和以密耳 (.001 英寸) 为单位测量的线使用相同的 epsilon。那只是.(这就是要求 epsilon 或最大 ULPS 的原因)。
a==b
1000a!=1000b
AlmostEqual2sComplement
对角度的余弦和线的长度都使用相同的 epsilon!
使用此类比较函数对集合中的项进行排序。(在这种情况下,使用内置的 C++ 运算符进行双精度会产生正确的结果。
==
就像我说的:这完全取决于上下文和 和 的预期大小。a
b
顺便说一句,是“机器epsilon”。它是 和 下一个值之间的差值,可以用双精度表示。我想它可以在比较函数中使用,但前提是预期值小于 1。(这是对@cdv回答的回应......std::numeric_limits<double>::epsilon()
1.0
此外,如果你基本上有算术(这里我们在某些情况下使用双精度来保存 int 值),你的算术将是正确的。例如,将与 相同。只要你不做导致分数 () 的事情,或者不超出 int 的大小。int
doubles
4.0/2.0
1.0+1.0
4.0/3.0
评论
fabs(a)+fabs(b)
float
double
epsilon
1/(2^24)*2^127
~2^103
epsilon
|a-b|<epsilon
比较浮点数取决于上下文。由于即使更改操作顺序也会产生不同的结果,因此了解您希望数字的“相等”程度非常重要。
在查看浮点比较时,布鲁斯·道森 (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 是什么)的好资源。
评论
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
救了我的命。大声笑 请注意,这个版本(我还没有检查是否也适用于其他版本)还考虑了浮点数的整数部分可能发生的变化(例如:您可以清楚地看到两个数字之间几乎存在差异),这非常好!当累积的舍入误差溢出数字的小数部分时,就会发生这种情况。2147352577.9999997616 == 2147352576.0000000000
2
std::max(std::abs(a), std::abs(b))
std::min()
std::abs
fabs
definitelyGreaterThan
对于绝对应该等于(即不大于)的事物,报告为 true。
我发现Google C++测试框架包含一个很好的基于模板的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: [email protected] (Zhanyong Wan), [email protected] (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 Test 源代码中获取最新版本,而不是我粘贴在这里的那个。AlmostEquals
评论
我会非常警惕这些涉及浮点减法的答案(例如,fabs(a-b) < epsilon)。首先,浮点数在更大的量级和足够高的量级下变得更加稀疏,其中间距大于 epsilon,您不妨只做一个 == b。其次,减去两个非常接近的浮点数(考虑到你正在寻找接近相等的浮点数,这些浮点数往往会如此)正是你获得灾难性取消的方式。
虽然不便携,但我认为格罗姆的答案在避免这些问题方面做得最好。
评论
a
b
不幸的是,即使是你的“浪费”代码也是不正确的。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
DBL_EPSILON
FLT_EPSILON
DBL_EPSILON
正如其他人所指出的,使用固定指数 epsilon(例如 0.0000001)对于远离 epsilon 值的值将毫无用处。例如,如果两个值是 10000.000977 和 10000,则这两个数字之间没有 32 位浮点值 -- 10000 和 10000.000977 尽可能接近,而不会逐位相同。在这里,小于 0.0009 的 epsilon 是没有意义的;您不妨使用直线相等运算符。
同样,当两个值的大小接近 epsilon 时,相对误差会增长到 100%。
因此,尝试将固定点数(如 0.00001)与浮点值(其中指数是任意的)混合在一起是毫无意义的。只有当您可以确信操作数值位于狭窄的域内(即接近某个特定指数),并且您为该特定测试正确选择了 epsilon 值时,这才有效。如果你从空中拉出一个数字(“嘿!0.00001 很小,所以那一定很好!“),你注定要出现数字错误。我花了很多时间调试糟糕的数字代码,其中一些糟糕的笨蛋抛入随机的 epsilon 值以使另一个测试用例工作。
如果你做任何类型的数值编程,并认为你需要达到定点ε,请阅读布鲁斯关于比较浮点数的文章。
我的班级基于之前发布的答案。与 Google 的代码非常相似,但我使用了一种偏见,将所有 NaN 值推到 0xFF000000 以上。这样可以更快地检查 NaN。
此代码旨在演示概念,而不是通用解决方案。谷歌的代码已经展示了如何计算所有平台特定的值,我不想重复所有这些值。我对此代码进行了有限的测试。
typedef unsigned int U32;
// Float Memory Bias (unsigned)
// ----- ------ ---------------
// NaN 0xFFFFFFFF 0xFF800001
// NaN 0xFF800001 0xFFFFFFFF
// -Infinity 0xFF800000 0x00000000 ---
// -3.40282e+038 0xFF7FFFFF 0x00000001 |
// -1.40130e-045 0x80000001 0x7F7FFFFF |
// -0.0 0x80000000 0x7F800000 |--- Valid <= 0xFF000000.
// 0.0 0x00000000 0x7F800000 | NaN > 0xFF000000
// 1.40130e-045 0x00000001 0x7F800001 |
// 3.40282e+038 0x7F7FFFFF 0xFEFFFFFF |
// Infinity 0x7F800000 0xFF000000 ---
// NaN 0x7F800001 0xFF000001
// NaN 0x7FFFFFFF 0xFF7FFFFF
//
// Either value of NaN returns false.
// -Infinity and +Infinity are not "close".
// -0 and +0 are equal.
//
class CompareFloat{
public:
union{
float m_f32;
U32 m_u32;
};
static bool CompareFloat::IsClose( float A, float B, U32 unitsDelta = 4 )
{
U32 a = CompareFloat::GetBiased( A );
U32 b = CompareFloat::GetBiased( B );
if ( (a > 0xFF000000) || (b > 0xFF000000) )
{
return( false );
}
return( (static_cast<U32>(abs( a - b ))) < unitsDelta );
}
protected:
static U32 CompareFloat::GetBiased( float f )
{
U32 r = ((CompareFloat*)&f)->m_u32;
if ( r & 0x80000000 )
{
return( ~r - 0x007FFFFF );
}
return( r + 0x7F800000 );
}
};
实际上,在数值软件中,您要检查两个浮点数是否完全相等。我发布了一个类似的问题
https://stackoverflow.com/a/10973098/1447411
所以你不能说“CompareDoubles1”一般是错误的。
评论
意识到这是一个古老的线程,但这篇文章是我在比较浮点数时发现的最直接的线程之一,如果您想探索更多内容,它也有更详细的参考资料,并且主站点涵盖了处理浮点数的完整问题浮点指南:比较。
我们可以在浮点公差中找到一篇更实用的文章,并指出有绝对公差测试,在 C++ 中可以归结为:
bool absoluteToleranceCompare(double x, double y)
{
return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}
和相对耐受性测试:
bool relativeToleranceCompare(double x, double y)
{
double maxXY = std::max( std::fabs(x) , std::fabs(y) ) ;
return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXY ;
}
文章指出,当 和 较大时,绝对测试失败,而当它们较小时,绝对测试在相对情况下失败。假设绝对公差和相对公差相同,组合测试将如下所示:x
y
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 ;
}
在数量规模方面:
如果在某种物理意义上,数量(即相对价值)的一小部分是可比的,那么我认为,以下情况是完全正确的:epsilon
A
B
#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;
}
我的方式可能不正确,但有用
将两个浮点数转换为字符串,然后进行字符串比较
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;
}
也可以进行操作员覆盖
评论
/// 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 测试它,这次我得到了预期的结果。
您不能将两个与固定的 .根据 的值,会有所不同。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;
}
以更通用的方式:
template <typename T>
bool compareNumber(const T& a, const T& b) {
return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}
注意:
正如@SirGuy所指出的,这种方法是有缺陷的。
我把这个答案留在这里,作为一个例子,不做。
评论
a
b
epsilon()
为什么不执行按位异或?如果两个浮点数的对应位相等,则它们相等。我认为,将指数位放在尾数之前的决定是为了加快两个浮点数的比较。 我认为,这里的许多答案都忽略了 epsilon 比较的重点。Epsilon 值仅取决于比较浮点数的精度。例如,在对浮点数进行一些算术运算后,你会得到两个数字:2.5642943554342 和 2.5642943554345。它们不相等,但对于解决方案,只有 3 位十进制数字很重要,因此它们相等:2.564 和 2.564。在本例中,选择等于 0.001 的 epsilon。使用按位 XOR 也可以进行 Epsilon 比较。如果我错了,请纠正我。
评论
我最终花了相当多的时间浏览这个伟大线程中的材料。我怀疑每个人都想花这么多时间,所以我会强调我学到的东西和我实施的解决方案的总结。
快速总结
- 1e-8 和 1e-16 大致相同吗?如果您正在查看嘈杂的传感器数据,那么可能是的,但如果您正在进行分子模拟,那么可能不是!底线:您始终需要在特定函数调用的上下文中考虑容差值,而不仅仅是使其成为通用的应用范围的硬编码常量。
- 对于常规库函数,具有默认容差的参数仍然很好。典型的选择是与 float.h 中的FLT_EPSILON相同。然而,这是有问题的,因为用于比较 1.0 等值的 epsilon 与用于 1E9 等值的 epsilon 不同。FLT_EPSILON是为 1.0 定义的。
numeric_limits::epsilon()
- 然而,检查数字是否在公差范围内的明显实现是,这不起作用,因为默认的 epsilon 是为 1.0 定义的。我们需要根据 a 和 b 来放大或缩小 epsilon。
fabs(a-b) <= epsilon
- 这个问题有两种解决方案:要么将 epsilon 设置为成比例,要么可以在 a 周围获得下一个可表示的数字,然后查看 b 是否落入该范围。前者称为“相对”方法,后者称为ULP方法。
max(a,b)
- 与 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
检查 ,这意味着 a 和 b 几乎相等(因此 a 不一定小于 b)。在这两种情况下检查差异>容差不是更有意义吗?或者添加一个参数来控制近似相等性检查是否应返回 true。diff < tolerance
orEqualTo
<
>
我使用以下代码:
bool AlmostEqual(double v1, double v2)
{
return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
}
评论
epsilon
epsilon
epsilon
std::fabs(std::min(v1, v2))
这证明使用不是答案 - 它失败了 大于 1 的值:std::numeric_limits::epsilon()
我上面评论的证明:
#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 且刚好大于 1),两个输入值尽可能接近,并且仍然比较为不接近。因此,对于大于 1.0 的值,不妨只使用相等性检验。修复的 epsilon 在比较浮点值时不会拯救您。
评论
return *(reinterpret_cast<double*>(&x));
numeric_limits<>::epsilon
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;
}
在以下位置找到了另一个有趣的实现: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";
}
这是 lambda 的另一种解决方案:
#include <cmath>
#include <limits>
auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };
评论
这个怎么样?
template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }
我见过各种方法——但从未见过这个,所以我也很想听听任何评论!
评论
A==B
std::abs(A-B) < epsilon
在此版本中,您检查这些数字彼此不同,而不是超过某些部分(例如,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;
}
请注意 Sneftel 关于浮点数可能存在分数限制的评论。
另请注意,它与绝对 epsilon 的方法不同 - 在这里你不关心“数量级” - 数字可能是,比如说,或者,它们将始终被一致地比较,并且您不必为每种情况更新 epsilon。1e100
1e-100
评论
您必须对浮点比较进行此处理,因为浮点数不能像整数类型那样完美比较。以下是各种比较运算符的函数。
浮点数等于 (==
)
我也更喜欢减法技术,而不是依赖 or ,但我必须在从 64 位 PC 到 ATMega328 微控制器 (Arduino) 的各种架构上对其进行快速分析,才能真正了解它是否会产生很大的性能差异。fabs()
abs()
所以,让我们忘掉所有这些绝对值的东西,做一些减法和比较!
/// @brief See if two floating point numbers are approximately equal.
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon A small value such that if the difference between the two numbers is
/// smaller than this they can safely be considered to be equal.
/// @return true if the two numbers are approximately equal, and false otherwise
bool is_float_eq(float a, float b, float epsilon) {
return ((a - b) < epsilon) && ((b - a) < epsilon);
}
bool is_double_eq(double a, double b, double epsilon) {
return ((a - b) < epsilon) && ((b - a) < epsilon);
}
用法示例:
constexpr float EPSILON = 0.0001; // 1e-4
is_float_eq(1.0001, 0.99998, EPSILON);
我不完全确定,但在我看来,对基于 epsilon 的方法的一些批评,正如这个高度赞成的答案下面的评论中所描述的那样,可以通过使用变量 epsilon 来解决,根据被比较的浮点值进行缩放,如下所示:
float a = 1.0001;
float b = 0.99998;
float epsilon = std::max(std::fabs(a), std::fabs(b)) * 1e-4;
is_float_eq(a, b, epsilon);
这样,epsilon 值会随着浮点值而缩放,因此永远不会太小以至于变得微不足道。
为了完整起见,让我们添加其余部分:
大于 () 和小于 ():>
<
/// @brief See if floating point number `a` is > `b`
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon a small value such that if `a` is > `b` by this amount, `a` is considered
/// to be definitively > `b`
/// @return true if `a` is definitively > `b`, and false otherwise
bool is_float_gt(float a, float b, float epsilon) {
return a > b + epsilon;
}
bool is_double_gt(double a, double b, double epsilon) {
return a > b + epsilon;
}
/// @brief See if floating point number `a` is < `b`
/// @param[in] a number 1
/// @param[in] b number 2
/// @param[in] epsilon a small value such that if `a` is < `b` by this amount, `a` is considered
/// to be definitively < `b`
/// @return true if `a` is definitively < `b`, and false otherwise
bool is_float_lt(float a, float b, float epsilon) {
return a < b - epsilon;
}
bool is_double_lt(double a, double b, double epsilon) {
return a < b - epsilon;
}
大于或等于 () 且小于或等于 (>=
<=
)
/// @brief Returns true if `a` is definitively >= `b`, and false otherwise
bool is_float_ge(float a, float b, float epsilon) {
return a > b - epsilon;
}
bool is_double_ge(double a, double b, double epsilon) {
return a > b - epsilon;
}
/// @brief Returns true if `a` is definitively <= `b`, and false otherwise
bool is_float_le(float a, float b, float epsilon) {
return a < b + epsilon;
}
bool is_double_le(double a, double b, double epsilon) {
return a < b + epsilon;
}
其他改进:
- 在 C++ 中,一个好的默认值是 ,其计算结果为 或 、 或 。请参阅此处:https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon。您还可以查看 、 和 的标题。
epsilon
std::numeric_limits<T>::epsilon()
0
FLT_EPSILON
DBL_EPSILON
LDBL_EPSILON
float.h
FLT_EPSILON
DBL_EPSILON
LDBL_EPSILON
- 您可以改为函数模板化,以处理所有浮点类型:、 和 ,并通过模板内部的 对这些类型进行类型检查。
float
double
long double
static_assert()
- 缩放值是一个好主意,以确保它适用于非常大和非常小的值。本文推荐并对其进行说明:http://realtimecollisiondetection.net/blog/?p=89。因此,您应该按等于 的缩放值来缩放 epsilon,如本文所述。否则,随着和/或幅度的增加,ε最终会相对于这些值变得非常小,以至于在浮点误差中丢失。因此,我们将其缩放以使其像它们一样变得更大。但是,使用 epsilon 作为允许的最小缩放因子还可以确保对于非常小的量级和值,epsilon 本身不会缩放得太小,以至于在浮点误差中也会丢失。因此,我们将最小比例因子限制为 。
epsilon
a
b
max(1.0, abs(a), abs(b))
a
b
1.0
a
b
1.0
- 如果要将上述函数“封装”到一个类中,请不要这样做。相反,如果您愿意,可以将它们包装在命名空间中,以便为它们设置命名空间。例如:如果您将所有独立函数放入名为 的命名空间中,则可以像这样访问该函数,例如: .
float_comparison
is_eq()
float_comparison::is_eq(1.0, 1.5);
- 添加与零的比较也可能很好,而不仅仅是两个值之间的比较。
- 因此,这里有一个更好的解决方案类型,有上述改进:
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
另请参阅:
- 在我的 repo 中上面一些函数的宏形式在这里:utilities.h。
- 2020 年 11 月 29 日更新:这是一个正在进行的工作,准备就绪后我将把它作为一个单独的答案,但我在这个文件中为所有 C 函数制作了一个更好的、缩放的 epsilon 版本:utilities.c。看一看。
- 补充阅读我现在
需要做的是:重新审视浮点公差,作者:Christer Ericson。非常有用的文章!它谈到了缩放 epsilon 以确保它永远不会在浮点误差中掉落,即使是对于非常大的幅度和/或值!a
b
我使用这段代码。与上述答案不同,这允许人们
给出代码注释中解释的 a。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 围绕直径为 x 的圆圈中,则认为两者相等。abs_relative_error
我使用以下函数进行浮点数比较:
bool approximatelyEqual(double a, double b)
{
return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * std::numeric_limits<double>::epsilon());
}
评论
<invoke Knuth>
</invoke Knuth>
==
==