【问题标题】:How does the C == operator decide whether or not two floating point values are equal?C == 运算符如何确定两个浮点值是否相等?
【发布时间】:2011-06-07 00:58:40
【问题描述】:

今天我正在追查为什么我的程序会出现一些意外的校验和不匹配错误,在我编写的一些代码中,以包含 32 位校验和值的格式序列化和反序列化 IEEE-754 浮点值(这是通过对浮点数组的字节运行 CRC 类型算法来计算的)。

经过一番摸索后,我意识到问题在于 0.0f 和 -0.0f 具有不同的位模式(分别为 0x00000000 与 0x00000080(小端序)),但它们被 C++ 相等性视为等效-操作员。因此,发生校验和不匹配错误是因为我的校验和计算算法发现了这两个位模式之间的差异,而我的代码库的某些其他部分(使用浮点相等测试,而不是逐字节查看值) byte) 没有做出这种区分。

好吧,很公平——无论如何,我应该知道最好不要进行浮点相等测试。

但这让我想到,是否有其他 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;
}

【问题讨论】:

  • how-to.wikia.com/wiki/… 仅供参考,使用 epsilon 是浮点比较的前进方向。不切题,但对了解很有用。
  • NaN 可以采用任何一种方式(可能取决于编译器)。它们在内存中也可能不同,因为存在大量可能的 NaN(单精度为 2^24-1)。

标签: c comparison floating-point equality ieee-754


【解决方案1】:

它使用 IEEE-754 相等规则。

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

【讨论】:

  • +1 NaN。请注意,在这种情况下,位模式将是相同,但== 将返回false。
  • +1 NaN。另请注意,您可以使用不同的位模式来表示两个 NaNs。
  • 确实,+0.0 和 -0.0 是唯一可以比较相等的两个不同的位模式。
  • @Ernest: minor nitpick: NaN 有很多位模式。对于 32 位浮点数,它将是 2^23 - 1 个不同的位模式。
【解决方案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");

【讨论】:

  • 显然这不是逐位级别的精确比较,或者它不会表明 (-0.0f == 0.0f) 因为这两个值具有不同的位模式。
  • 一个合理的观点,如果我写“精确的一点一点比较”会更合适。
【解决方案3】:

对于 Windows 平台,this link has:

  • 除以 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(任何 OP)任何值 = NaN
  • 当一个或两个操作数都是 NaN 时,比较 EQ、GT、GE、LT 和 LE 返回 FALSE。
  • 比较忽略 0 的符号(因此 +0 等于 -0)。
  • 当一个或两个操作数都是 NaN 时,比较 NE 返回 TRUE。
  • 任何非 NaN 值与 +/- INF 的比较都会返回正确的结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 2016-11-14
    相关资源
    最近更新 更多