使用整数表示和 ULP 的别名正确比较双打
Comparing doubles properly using aliasing with integer representations and ULPs
我试图避免使用 epsilon 比较来比较浮点类型。我能想到的最佳解决方案是使用 ULP 的差异(单位在最后),尽管 this article 使用整数表示有更好的解决方案(///
表示我自己的评论):
/* See
https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/
for the potential portability problems with the union and bit-fields below.
*/
#include <stdint.h> // For int32_t, etc.
union Float_t
{
Float_t(float num = 0.0f) : f(num) {}
// Portable extraction of components.
bool Negative() const { return i < 0; }
int32_t RawMantissa() const { return i & ((1 << 23) - 1); }
int32_t RawExponent() const { return (i >> 23) & 0xFF; }
int32_t i; /// Perhaps overflow when using doubles?
float f;
#ifdef _DEBUG
struct
{ // Bitfields for exploration. Do not use in production code.
uint32_t mantissa : 23; /// 52 for double?
uint32_t exponent : 8; /// 11 for double?
uint32_t sign : 1;
} parts;
#endif
};
bool AlmostEqualUlps(float A, float B, int maxUlpsDiff)
{
Float_t uA(A);
Float_t uB(B);
// Different signs means they do not match.
if (uA.Negative() != uB.Negative())
{
// Check for equality to make sure +0==-0
if (A == B)
return true;
return false;
}
// Find the difference in ULPs.
int ulpsDiff = abs(uA.i - uB.i);
if (ulpsDiff <= maxUlpsDiff)
return true;
return false;
}
但是,我似乎无法以支持双打的方式重新格式化代码。我什至仔细阅读了解释,found here。
有谁知道解决这个问题的最佳方法是什么?
在任何人决定将此标记为重复之前:不要,因为唯一类似的问题是针对 javascript 的,而 C++ 答案是:
bool IsAlmostEqual(double A, double B)
{
//http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
long long aInt = reinterpret_cast<long long&>(A);
if (aInt < 0) aInt = -9223372036854775808LL - aInt;
long long bInt = reinterpret_cast<long long&>(B);
if (bInt < 0) bInt = -9223372036854775808LL - bInt;
return (std::abs(aInt - bInt) <= 10000);
}
它不使用 ULP,而是某种绑定,我完全不确定 -9223372036854775808LL - aInt
是什么(可能是 int64 溢出的地方)。
我认为您的代码根本不起作用。下面只是一个它会出错的例子。 (这不是答案,但解释太长,无法放入评论)
int main()
{
Float_t x;
Float_t y;
x.i = 0x7F800000;
y.i = 0x7F7FFFFF;
AlmostEqualUlps(x.f, y.f, 1); // return true
}
然而,x.f
实际上是无穷大而y.f
是FLT_MAX
。任何定义的差异都是无穷大。除非这是您的预期行为,否则认为有限数和无穷大几乎相等。您对 ULP 的实施完全不合时宜。事实上,对于上面的两个数字,ULP 甚至都没有明确定义。
另一个例子是 0x7F800000(或任何接近于此的数字,具体取决于您的 maxULP
)和 0x7F800001 (NaN)。与上面的示例不同,甚至没有理由认为应该考虑 "almost equal".
另一方面,您拒绝任何具有不同符号的对,因为它们不够接近,而实际上 -FLT_MIN
和 FLT_MIN
之间存在很多次正规,这可能被认为是 "almost equal"。例如,0x80000001 和 0x1 相差 2ULP,但如果您在函数中将 maxULP
设置为 2,它将 return false。
如果您可以排除非正规数、无穷大、NaN,那么要处理 double,您只需将 uint32_t
替换为 uint64_t
,如其他人评论中所述。
我试图避免使用 epsilon 比较来比较浮点类型。我能想到的最佳解决方案是使用 ULP 的差异(单位在最后),尽管 this article 使用整数表示有更好的解决方案(///
表示我自己的评论):
/* See
https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/
for the potential portability problems with the union and bit-fields below.
*/
#include <stdint.h> // For int32_t, etc.
union Float_t
{
Float_t(float num = 0.0f) : f(num) {}
// Portable extraction of components.
bool Negative() const { return i < 0; }
int32_t RawMantissa() const { return i & ((1 << 23) - 1); }
int32_t RawExponent() const { return (i >> 23) & 0xFF; }
int32_t i; /// Perhaps overflow when using doubles?
float f;
#ifdef _DEBUG
struct
{ // Bitfields for exploration. Do not use in production code.
uint32_t mantissa : 23; /// 52 for double?
uint32_t exponent : 8; /// 11 for double?
uint32_t sign : 1;
} parts;
#endif
};
bool AlmostEqualUlps(float A, float B, int maxUlpsDiff)
{
Float_t uA(A);
Float_t uB(B);
// Different signs means they do not match.
if (uA.Negative() != uB.Negative())
{
// Check for equality to make sure +0==-0
if (A == B)
return true;
return false;
}
// Find the difference in ULPs.
int ulpsDiff = abs(uA.i - uB.i);
if (ulpsDiff <= maxUlpsDiff)
return true;
return false;
}
但是,我似乎无法以支持双打的方式重新格式化代码。我什至仔细阅读了解释,found here。
有谁知道解决这个问题的最佳方法是什么?
在任何人决定将此标记为重复之前:不要,因为唯一类似的问题是针对 javascript 的,而 C++ 答案是:
bool IsAlmostEqual(double A, double B)
{
//http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
long long aInt = reinterpret_cast<long long&>(A);
if (aInt < 0) aInt = -9223372036854775808LL - aInt;
long long bInt = reinterpret_cast<long long&>(B);
if (bInt < 0) bInt = -9223372036854775808LL - bInt;
return (std::abs(aInt - bInt) <= 10000);
}
它不使用 ULP,而是某种绑定,我完全不确定 -9223372036854775808LL - aInt
是什么(可能是 int64 溢出的地方)。
我认为您的代码根本不起作用。下面只是一个它会出错的例子。 (这不是答案,但解释太长,无法放入评论)
int main()
{
Float_t x;
Float_t y;
x.i = 0x7F800000;
y.i = 0x7F7FFFFF;
AlmostEqualUlps(x.f, y.f, 1); // return true
}
然而,x.f
实际上是无穷大而y.f
是FLT_MAX
。任何定义的差异都是无穷大。除非这是您的预期行为,否则认为有限数和无穷大几乎相等。您对 ULP 的实施完全不合时宜。事实上,对于上面的两个数字,ULP 甚至都没有明确定义。
另一个例子是 0x7F800000(或任何接近于此的数字,具体取决于您的 maxULP
)和 0x7F800001 (NaN)。与上面的示例不同,甚至没有理由认为应该考虑 "almost equal".
另一方面,您拒绝任何具有不同符号的对,因为它们不够接近,而实际上 -FLT_MIN
和 FLT_MIN
之间存在很多次正规,这可能被认为是 "almost equal"。例如,0x80000001 和 0x1 相差 2ULP,但如果您在函数中将 maxULP
设置为 2,它将 return false。
如果您可以排除非正规数、无穷大、NaN,那么要处理 double,您只需将 uint32_t
替换为 uint64_t
,如其他人评论中所述。