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

Comparing IEEE floats and doubles for equality

提问人:Craig H 提问时间:8/22/2008 更新时间:7/3/2012 访问量:5840

问:

比较IEEE浮点数和双精度相等的最佳方法是什么?我听说过几种方法,但我想看看社区是怎么想的。

变量 浮点 不等式

评论

0赞 grom 8/22/2008
查看类似问题的答案

答:

7赞 DrPizza 8/22/2008 #1

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

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

类似的技术可用于双打。诀窍是转换浮点数,使它们有序(就像整数一样),然后看看它们有多大的不同。

我不知道为什么这个该死的东西会搞砸我的下划线。编辑:哦,也许这只是预览的人工制品。那没关系。

评论

0赞 OJ. 10/8/2008
这在精确度方面赢得了毋庸置疑的胜利。但就性能而言......为了提高速度,你必须牺牲一点准确性。同意?
1赞 skrebbel 8/6/2010
如果你需要双打或可移植性:我找到了一个很好的跨平台实现,可以在 Google Test 中处理双打和浮点数,并将其发布在这里:stackoverflow.com/questions/17333/......
0赞 Mat Noguchi 8/22/2008 #2

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

0赞 DrPizza 8/22/2008 #3

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

即使它导致它通过内存从向量寄存器复制到整数寄存器,即使它使管道停止,这也是我遇到的最好的方法,因为它提供了最强大的比较,即使面对浮点错误。

也就是说,这是一个值得付出的代价。

评论

0赞 supercat 4/4/2015
我怀疑在许多处理器上,这将比任何涉及浮点位到 int 强制转换的东西都快,尽管需要像上面这样的表达式真的让我很痛苦。我想知道 Nan!=NaN 的决定提供了多少好处,以及它在浪费代码和调试时间方面花费了多少?(a==b || (a!=a && b != b))
3赞 Craig H 8/22/2008 #4

我使用的当前版本是这个

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 方法更好吗?如果是这样,为什么?

0赞 Mat Noguchi 8/22/2008 #5

这是我遇到的最好的方法,因为即使面对浮点误差,它也能提供最可靠的比较。

如果你有浮点错误,你就会遇到比这更多的问题。虽然我想这取决于个人观点。

0赞 DrPizza 8/22/2008 #6

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

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

0赞 DrPizza 8/22/2008 #7

如果你有浮点错误,你就会遇到比这更多的问题。虽然我想这取决于个人观点。

即使我们进行数值分析以尽量减少误差的累积,我们也无法消除它,我们可能得到的结果应该是相同的(如果我们用实数计算),但又不同(因为我们不能用实数计算)。

0赞 Nick 8/22/2008 #8

如果你正在寻找两个相等的浮点数,那么在我看来它们应该是相同的。如果您遇到浮点舍入问题,也许定点表示更适合您的问题。

评论

0赞 BCS 12/10/2008
处理(几乎)相等是循环中的常见情况,当事情变得“足够接近”时,你想停下来,但你不希望它们完全收敛。
0赞 DrPizza 8/22/2008 #9

如果你正在寻找两个相等的浮点数,那么在我看来它们应该是相同的。如果您遇到浮点舍入问题,也许定点表示更适合您的问题。

也许我们无法承受这种方法造成的范围或性能损失。

0赞 Craig H 8/22/2008 #10

如果你正在寻找两个相等的浮点数,那么在我看来它们应该是相同的。如果您遇到浮点舍入问题,也许定点表示更适合您的问题。

也许我应该更好地解释这个问题。在 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”。你是说应该吗?

评论

0赞 Jim Kramer 6/2/2009
由于浮点精度和舍入误差,您几乎总是会得到 != b。
0赞 Nick 8/22/2008 #11

@DrPizza:我不是性能专家,但我希望定点运算比浮点运算更快(在大多数情况下)。

@Craig H:当然。我完全可以打印它。如果 a 或 b 存储货币,则它们应该以固定点表示。我很难想出一个真实世界的例子,其中这种逻辑应该与浮点数相关联。适合花车的东西:

  • 权重
  • 行列
  • 距离
  • 真实世界的值(例如来自 ADC)

对于所有这些事情,要么你然后数字并简单地将结果呈现给用户进行人工解释,要么你做一个比较陈述(即使这样的陈述是,“这个东西在另一个东西的 0.001 以内”)。像我这样的比较陈述仅在算法的上下文中有用:“在 0.001 以内”部分取决于您要问的物理问题。我的 0.02。或者我应该说 2/100?

1赞 DrPizza 8/22/2008 #12

@DrPizza:我不是性能专家,但我希望定点运算比浮点运算更快(在大多数情况下)。

这取决于你对它们做了什么。与 IEEE 浮点数具有相同范围的定点类型会慢很多倍(并且大很多倍)。

适合花车的东西:

3D图形、物理/工程、模拟、气候模拟......

0赞 Nick 8/22/2008 #13

这取决于你是什么 和他们一起做。定点类型 与IEEE浮点数相同的范围 会慢很多倍(和 大很多倍)。

好的,但是如果我想要一个极小的位分辨率,那么它又回到了我原来的观点:==和!=在这样的问题的上下文中没有意义。

int 允许我表达 ~10^9 个值(无论范围如何),这对于我关心其中两个相等的任何情况来说似乎都足够了。如果这还不够,请使用 64 位操作系统,您将获得大约 10^19 个不同的值。

我可以在 int 中表示 0 到 10^200 的范围(例如),只是位分辨率受到影响(分辨率将大于 1,但同样,没有应用程序具有这种范围和那种分辨率)。

总而言之,我认为在所有情况下,要么表示值的连续体,在这种情况下 != 和 == 是无关紧要的,要么表示一组固定的值,这些值可以映射到 int(或其他固定精度类型)。

0赞 jakobengblom2 10/9/2008 #14

int 让我表示 ~10^9 个值 (无论范围如何)似乎 对于我的任何情况都足够了 会关心他们中的两个 平等。如果这还不够,请使用 64 位操作系统,你有大约 10^19 非重复值。

我实际上已经达到了那个极限......我试图在模拟中处理以 ps 为单位的时间和以时钟周期为单位的时间,您可以轻松达到 10^10 个周期。无论我做什么,我都很快溢出了 64 位整数的微不足道的范围......10^19 并不像你想象的那么多,现在 128 位计算!

浮点数使我能够解决数学问题,因为这些值在低端溢出了很多零。因此,您基本上在数字中有一个小数点浮动 aronud,而不会损失精度(与 64 位 int 相比,我可以喜欢浮点数中允许的不同值数量更有限,但迫切需要 th 范围!

然后东西转换回整数进行比较等。

很烦人,最后我放弃了整个尝试,只是依靠浮子、<和>来完成工作。不完美,但适用于设想的用例。

1赞 Michael Lehn 6/11/2012 #15

在数值软件中,您经常要测试两个浮点数是否完全相等。LAPACK充满了此类案例的例子。当然,最常见的情况是你想测试浮点数是否等于“零”、“一”、“二”、“一半”。如果有人有兴趣,我可以选择一些算法并更详细地介绍。

此外,在 BLAS 中,您经常需要检查浮点数是正好是 Zero 还是 One。例如,例程 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充满了不可能的例子。

附言:

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

  • 尽管 LAPACK 是用 Fortran 编写的,但如果您将其他编程语言用于数值软件,则逻辑是相同的。