C == 操作符如何判断两个浮点数是否相等?

7
今天我在追踪我的程序为什么会收到一些意外的校验和不匹配错误。这些错误出现在我编写的代码中,用于序列化和反序列化IEEE-754浮点值,以包括32位校验和值(该值通过在浮点数数组的字节上运行CRC类型算法来计算)的格式。
经过一番思考,我意识到问题在于0.0f和-0.0f具有不同的位模式(分别是0x00000000和0x00000080(小端),但它们被C++等号运算符视为相等)。因此,校验和不匹配错误发生了,因为我的校验和计算算法捕捉了这两个位模式之间的差异,而我的代码库中某些其他部分(使用浮点等式测试,而不是逐字节查看值)没有做出这种区分。
好吧,说得对——我应该知道不要进行浮点等式测试。
但是这让我想到了,是否还有其他IEEE-754浮点值被认为是相等的(根据C == 运算符),但具有不同的位模式?或者换句话说,== 运算符如何确定两个浮点数是否相等?新手我认为它正在执行类似于memcmp()的操作,但显然它比那更微妙。
以下是我上面所说的代码示例,以防我没有表达清楚。
#include <stdio.h>

static void PrintFloatBytes(const char * title, float f)
{
   printf("Byte-representation of [%s] is: ", title);
   const unsigned char * p = (const unsigned char *) &f;
   for (int i=0; i<sizeof(f); i++) printf("%02x ", p[i]);
   printf("\n");
}

int main(int argc, char ** argv)
{
   const float pzero = -0.0f;
   const float nzero = +0.0f;
   PrintFloatBytes("pzero", pzero);
   PrintFloatBytes("nzero", nzero);
   printf("Is pzero equal to nzero?  %s\n", (pzero==nzero)?"Yes":"No");
   return 0;
}

FYI,在C编程语言中使用epsilon是比较浮点数的正确方法。虽然不是主题,但这是有用的知识。 - user244343
1
NaN(非数字)可能会有不同的结果(可能取决于编译器)。由于存在大量可能的NaN(单精度为2^24-1),它们在内存中也可能不同。 - ughoavgfhw
3个回答

13

它使用IEEE-754的相等规则。

  • -0 == +0
  • NaN != NaN

2
+1 NaN。请注意,这是一种情况,其中位模式将是相同的,但“==”将返回“false”。 - Ernest Friedman-Hill
1
+1 NaN。同时请注意,可以有不同的位模式表示这两个NaN - trutheality
实际上,+0.0和-0.0是唯一可以相等比较的两个不同位模式。 - R.. GitHub STOP HELPING ICE
4
@Ernest:小问题:NaN 有很多位模式。对于32位浮点数,它将有2^23-1个不同的位模式。 - Alok Singhal

2

精确比较。因此最好避免使用==作为浮点数的测试。它可能会导致意外和微妙的错误。

一个标准的例子是这段代码:

 float f = 0.1f;

 if((f*f) == 0.01f)
     printf("0.1 squared is 0.01\n");
 else
     printf("Surprise!\n");

由于0.1在二进制中无法精确表示(它是一个重复的小数二进制),0.1*0.1不会完全等于0.01,因此等式测试不起作用。

数值分析师长期以来一直担心这个问题,但对于第一次近似,定义一个值很有用--APL称其为FUZZ--它是两个浮点数需要接近多少才能被认为相等。因此,您可以例如 #define FUZZ 0.00001f 并进行测试。

 float f = 0.1f;

 if(abs((f*f)-0.01f) < FUZZ)
     printf("0.1 squared is 0.01\n");
 else
     printf("Surprise!\n");

6
显然,在逐位比较时,它们并不完全相同,否则它不会表明(-0.0f == 0.0f),因为这两个值具有不同的位模式。 - Jeremy Friesner
一个合理的观点,如果我写下“逐位比较”,那将更为恰当。 - Charlie Martin

1

对于Windows平台,此链接包含以下内容:

  • 除以0会产生+/- INF,但0/0的结果为NaN。
  • (+/-) 0的对数为-INF。负值(除-0外)的对数为NaN。
  • 负数的倒数平方根(rsq)或平方根(sqrt)会产生NaN。例外是-0;sqrt(-0)产生-0,rsq(-0)产生-INF。
  • INF - INF = NaN
  • (+/-)INF / (+/-)INF = NaN
  • (+/-)INF * 0 = NaN
  • NaN(任何操作)任何值= NaN
  • 当一个或两个操作数为NaN时,EQ、GT、GE、LT和LE比较返回FALSE。
  • 比较忽略0的符号(因此+0等于-0)。
  • 任何非NaN值与+/- INF的比较都会返回正确的结果。

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