比较IEEE浮点数和双精度浮点数的相等性

9

如何最好地比较IEEE float和double的相等性?我听说过几种方法,但我想知道社区的想法。


请查看类似问题的这个答案 - grom
15个回答

7

我认为最好的方法是比较 ULPs

bool is_nan(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) == 0x7f800000 && (*reinterpret_cast<unsigned __int32*>(&f) & 0x007fffff) != 0;
}

bool is_finite(float f)
{
    return (*reinterpret_cast<unsigned __int32*>(&f) & 0x7f800000) != 0x7f800000;
}

// if this symbol is defined, NaNs are never equal to anything (as is normal in IEEE floating point)
// if this symbol is not defined, NaNs are hugely different from regular numbers, but might be equal to each other
#define UNEQUAL_NANS 1
// if this symbol is defined, infinites are never equal to finite numbers (as they're unimaginably greater)
// if this symbol is not defined, infinities are 1 ULP away from +/- FLT_MAX
#define INFINITE_INFINITIES 1

// test whether two IEEE floats are within a specified number of representable values of each other
// This depends on the fact that IEEE floats are properly ordered when treated as signed magnitude integers
bool equal_float(float lhs, float rhs, unsigned __int32 max_ulp_difference)
{
#ifdef UNEQUAL_NANS
    if(is_nan(lhs) || is_nan(rhs))
    {
        return false;
    }
#endif
#ifdef INFINITE_INFINITIES
    if((is_finite(lhs) && !is_finite(rhs)) || (!is_finite(lhs) && is_finite(rhs)))
    {
        return false;
    }
#endif
    signed __int32 left(*reinterpret_cast<signed __int32*>(&lhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(left < 0)
    {
        left = 0x80000000 - left;
    }
    signed __int32 right(*reinterpret_cast<signed __int32*>(&rhs));
    // transform signed magnitude ints into 2s complement signed ints
    if(right < 0)
    {
        right = 0x80000000 - right;
    }
    if(static_cast<unsigned __int32>(std::abs(left - right)) <= max_ulp_difference)
    {
        return true;
    }
    return false;
}

类似的技巧也可以用于双精度浮点数。关键是将浮点数转换为有序的(就像整数一样),然后看它们之间的差异。

我不知道为什么这个该死的东西会弄乱我的下划线。编辑:哦,也许这只是预览的副作用。那就没事了。


这个方案在精度方面毫无疑问是最好的。但就性能而言……你需要在速度提升和一定精度损失之间做出权衡。同意吗? - OJ.
1
如果您需要使用双精度或可移植性:我在Google Test中找到了一个很好的跨平台实现,可以处理双精度和浮点数,并在此处发布了它的链接:https://dev59.com/P3VD5IYBdhLWcg3wU5-H#3423299 - skrebbel

3
我使用的当前版本是这个。
bool is_equals(float A, float B,
               float maxRelativeError, float maxAbsoluteError)
{

  if (fabs(A - B) < maxAbsoluteError)
    return true;

  float relativeError;
  if (fabs(B) > fabs(A))
    relativeError = fabs((A - B) / B);
  else
    relativeError = fabs((A - B) / A);

  if (relativeError <= maxRelativeError)
    return true;

  return false;
}

通过相对误差和绝对误差容限的组合,这似乎解决了大多数问题。那么 ULP 方法更好吗?如果是,为什么?


1
@DrPizza:我不是一个性能大师,但在大多数情况下,我会期望固定点运算比浮点运算快。

这取决于你对它们的使用。与IEEE浮点型具有相同范围的固定点类型会慢得多(而且大得多)。
适合浮点数的事物包括:三维图形、物理/工程学、模拟、气候模拟等等。

1
在数值软件中,您经常需要测试两个浮点数是否完全相等。LAPACK充满了这种情况的例子。当然,最常见的情况是您想测试一个浮点数是否等于“零”、“一”、“二”、“半”。如果有人感兴趣,我可以选择一些算法并深入探讨。
此外,在BLAS中,您经常需要检查浮点数是否完全为零或一。例如,例程dgemv可以计算以下形式的操作:
  • y = beta*y + alpha*A*x
  • y = beta*y + alpha*A^T*x
  • y = beta*y + alpha*A^H*x
因此,如果beta等于1,则具有“加法赋值”,如果beta等于0,则具有“简单赋值”。因此,如果您对这些(常见)情况进行特殊处理,肯定可以降低计算成本。
当然,您可以设计BLAS例程,以便避免精确比较(例如使用某些标志)。但是,LAPACK充满了无法避免这种情况的例子。

P.S.:

  • 当然有很多情况下你不想检查“是否完全相等”。对于许多人来说,这甚至可能是他们需要处理的唯一情况。我想指出的是还有其他情况。

  • 虽然 LAPACK 是用 Fortran 编写的,但如果您在使用其他编程语言进行数值软件开发时,逻辑是相同的。


0
如果您想让两个浮点数相等,那么在我的看法中它们应该是完全相等的。如果您遇到了浮点舍入问题,也许固定点表示法会更适合您的问题。
也许我应该更好地解释一下这个问题。在C++中,以下代码:
#include <iostream>

using namespace std;


int main()
{
  float a = 1.0;
  float b = 0.0;

  for(int i=0;i<10;++i)
  {
    b+=0.1;
  }

  if(a != b)
  {
    cout << "Something is wrong" << endl;
  }

  return 1;
}

打印出短语“Something is wrong”。你是在说它应该这样吗?


由于浮点数精度和舍入误差,您几乎总会得到 a != b。 - Jim Kramer

0
一个int让我能够表达约10^9个值(无论范围如何),这似乎足以应对任何我关心两个值相等的情况。如果这还不够,使用64位操作系统,你就有大约10^19个不同的值。
实际上,我已经达到了这个限制......我试图在模拟中处理ps和时钟周期之间的时间,其中很容易达到10^10个周期。无论我做什么,我都很快就会溢出64位整数的微小范围...... 10^19并不像你想象的那样多,现在给我128位的计算吧!
浮点数允许我解决数学问题,因为值在低端有很多零而溢出。所以你基本上有一个十进制点在数字中漂移,没有精度损失(与64位整数允许的幂次数的有限数量相比,我可以接受更有限的尾数值,但是迫切需要更大的范围!)。
然后将事物转换回整数进行比较等操作。

非常烦人,最终我放弃了整个尝试,只依赖浮点数和<和>来完成工作。虽不完美,但能满足预期用例。


0

哦,亲爱的上帝,请不要将浮点位解释为整数,除非您正在运行 P6 或更早版本。


0
亲爱的上帝,请不要将浮点位解释为整数,除非您正在运行P6或更早版本的计算机。
即使它会通过内存从向量寄存器复制到整数寄存器,即使它会阻塞流水线,但这是我找到的最好的方法,因为它在面对浮点错误时提供了最强大的比较能力。
也就是说,这是值得付出代价的。

我怀疑在许多处理器上,(a==b || (a!=a && b != b)) 比任何涉及浮点位到整数转换的操作都要快,尽管像上面这样的表达式确实让我感到痛苦。我想知道 NaN!=NaN 决策提供了多少好处,以及它在浪费代码和调试时间方面造成了多少代价? - supercat

0
这是我遇到的最好的方法,因为即使在浮点误差的情况下,它也提供了最强大的比较。如果你有浮点误差,那么你会面临更多的问题。虽然我猜这取决于个人的观点。

0

通过结合相对误差和绝对误差容限,这似乎解决了大多数问题。ULP方法更好吗?如果是,为什么?

ULP是两个浮点数之间“距离”的直接度量。这意味着它们不需要您想出相对和绝对误差值,也不需要确保获得那些值“准确无误”。使用ULP,您可以直接表达您希望数字之间的接近程度,并且相同的阈值对于小值和大值同样有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接