欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

浮点数比较:判断两个浮点数是否相等(近似)

程序员文章站 2022-04-02 10:04:00
...

上大学时看林锐博士的《高质量C/C++编程指南》,里面提到了浮点数比较方法,现在回过头来看有点问题。

下面是我从原文摘抄的

4.3.3 浮点变量与零值比较
   【规则4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较。
	千万要留意,无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
	假设浮点变量的名字为x,应当将	
if (x == 0.0) 	// 隐含错误的比较
转化为 
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON是允许的误差(即精度)。

浮点数系统不能精确表示数轴上的每一个点,只能表示部分的点,并且不是均匀的,越接近0就越稠密,越远离0就越稀疏。

既然不能精确的表示,肯定会出现误差,比如

double a = 1.6;
double b = 0.3;
double c = a + b;
double d = 1.9;

c == d; // false

1.6 + 0.3 竟然不等于 1.9,这是这么回事?

因为精度的误差,运算出来的c 和 d 是数轴上浮点数点的集合里 相邻的两个点,而不是一个点。 

问我怎么知道c和d相邻的?通过printf %a 打印出的浮点数二进制表示法可以证明


  printf("%a\n", c); // 0x1.e666666666667p+0
  printf("%a\n", d); // 0x1.e666666666666p+0
  

林锐博士的方法是定义一个精度,在精度范围内就认为是相等的。

这个办法表面上看是可以的,但是,不要忘了我前面提到的 “浮点数系统不能精确表示数轴上的每一个点,只能表示部分的点,并且不是均匀的,越接近0就越稠密,越远离0就越稀疏。

上面的代码略加改动

  double w = 1ULL<<63;
  double a = 1.6 * w;
  double b = 0.3 * w;
  double c = a + b;
  double d = 1.9 * w;

  c == d; // false
  c - d;  // = 2048

  printf("%a\n", c); // 0x1.e666666666667p+63
  printf("%a\n", d); // 0x1.e666666666666p+63

现在,c和d仍然是数轴上相邻的两个点,但是它俩相差2048, 如果采用林锐博士的方法就很难决定 EPSINON 的取值到底是多少合适。

 

我变通了一下比较浮点数相等(近似)的方法:

    1)先通过常规方法比较,如果相等则返回相等(这样解决了 +0 -0 比较的问题)

    2) 如果常规方法不等,那么当符号位、阶码、最后一位以外的尾数都相同时,就认为相等

#include <stdint.h>

static inline int is_float64_eq(double x, double y) {
    return x == y || (*(uint64_t*)&x ^ *(uint64_t*)&y) == 1;
}

如果想把相邻的数改成相邻的几个数,微调 == 1 为 <4 或者 <8 ... 即可

 

如果考虑周全一点,考虑到有NaN时总是返回false,那么代码需要这样改一改

#include <stdint.h>

static inline int is_float64_eq(double x, double y) {
    return x == y || (
        (*(uint64_t*)&x ^ *(uint64_t*)&y) == 1 &&
        (*(uint64_t*)&x & 0x7ff0000000000000) != 0x7ff0000000000000);
}

 

另外需要特别说明的是:is_float64_eq 适用于比较非零的两个浮点数 

所以 is_float64_eq (x, y) 的效果 会比 is_float64_eq(x - y, 0) 效果好

转载于:https://my.oschina.net/2bit/blog/3065096