浮点数比较:判断两个浮点数是否相等(近似)
上大学时看林锐博士的《高质量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
上一篇: 判断两个数是否相等(浮点数比较)