浮点数比较:为什么不能直接用 == 比较浮点数?
这个问题,我几乎每次面试新人都会问。十个人里有八个会答「因为精度问题」,但再追问一句「具体怎么个精度问题」,能说清楚的就很少了。
我自己刚入行那会儿也踩过这个坑。有一次调试一个电机控制程序,明明计算出来的速度应该是 1.0,结果 if 判断死活进不去。折腾了一下午,最后发现是浮点数比较的问题。嗯,从那以后,我对浮点数就多了一份敬畏。
一、浮点数的本质:二进制近似
先问一个问题:0.1 在计算机里能精确表示吗?
答案是不能。
我们人类习惯用十进制,但计算机内部是二进制。十进制小数转二进制,用的是「乘 2 取整」法。你试试把 0.1 转成二进制:
0.1 × 2 = 0.2 → 取整数部分 0
0.2 × 2 = 0.4 → 取整数部分 0
0.4 × 2 = 0.8 → 取整数部分 0
0.8 × 2 = 1.6 → 取整数部分 1
0.6 × 2 = 1.2 → 取整数部分 1
0.2 × 2 = 0.4 → 取整数部分 0
…… 无限循环
看到了吗?0.1 的二进制是无限循环小数:0.00011001100110011...。而计算机的 float 只有 32 位,double 只有 64 位,根本存不下无限循环。所以只能截断,这就产生了误差。
核心结论:绝大多数十进制小数,在二进制里都是近似值。两个近似值做 == 比较,结果不可靠。
二、一个让你印象深刻的例子
来,看这段代码。我建议你亲手跑一下:
#include <stdio.h>
int main() {
float a = 0.1;
float b = 0.1;
if (a == b) {
printf("相等\n");
} else {
printf("不相等\n");
}
// 再看这个
float c = 0.1;
double d = 0.1;
if (c == d) {
printf("float 和 double 相等\n");
} else {
printf("float 和 double 不相等\n");
}
return 0;
}
第一个比较,a 和 b 都是 float,都从 0.1 赋值,结果大概率是「相等」。但第二个比较,float 和 double 比较,结果很可能是「不相等」。
为什么会这样?因为 float 的 0.1 和 double 的 0.1,精度不同,截断的位置不同,存储的近似值也不同。你拿两个不同精度的近似值去比,当然不相等。
注意:即使两个值看起来一样,只要类型不同(float vs double),或者计算路径不同(比如一个直接赋值,一个经过运算),比较结果都可能出乎意料。
三、正确的比较方式:用误差范围
那浮点数到底该怎么比?
说白了,不要比「相等」,要比「足够接近」。
标准做法是:计算两个数的差,看这个差的绝对值是否小于一个很小的阈值。
#include <stdio.h>
#include <math.h> // 用 fabs
#define EPSILON 1e-6
int main() {
float a = 0.1 * 3; // 0.3
float b = 0.3; // 直接赋值
// 错误做法
if (a == b) {
printf("直接比较:相等\n");
} else {
printf("直接比较:不相等\n"); // 很可能走这里
}
// 正确做法
if (fabs(a - b) < EPSILON) {
printf("误差比较:相等\n");
} else {
printf("误差比较:不相等\n");
}
return 0;
}
这里 EPSILON 取多大,要看你的应用场景。一般工程上用 1e-6 或 1e-7 就够了。如果是金融计算,可能需要 1e-9 甚至更小。但也不能太小,否则又回到了「直接比较」的老路上。
我的习惯:在嵌入式项目中,我通常用 1e-5 作为默认阈值。如果遇到传感器数据,噪声本身就大,我会放宽到 1e-3。如果是 PID 控制器的积分项,我会用 1e-8。没有万能阈值,要根据场景调。
四、避坑指南:我曾经踩过的雷
我曾经在一个数据采集项目中,需要判断两个浮点数是否相等,用来触发一个报警。当时图省事,直接用了 ==。结果呢?该报警的时候不报警,不该报警的时候乱报警。
排查了一整天,最后发现是浮点数比较的问题。两个传感器采集同一物理量,一个经过了一次滤波,另一个没滤波,数值上差了 0.000001。就这 1e-6 的差异,让 == 判断为不相等。
从那以后,我给自己定了个规矩:凡是浮点数比较,一律用误差范围,绝不偷懒用 ==。
五、特殊情况:什么时候可以用 ==?
也不是完全不能用。有几种特殊情况,用 == 是安全的:
- 和 0 比较:如果变量是从整数赋值来的,比如
float x = 0;,那么x == 0.0f是安全的。因为 0 在二进制里可以精确表示。 - 和整数比较:如果浮点数是从整数转换来的,且没有经过运算,比如
float y = 5;,那么y == 5.0f也是安全的。 - 特殊值:判断是否为 NaN(Not a Number)或无穷大时,可以用
isnan()和isinf()函数,不要自己比较。
但说实话,我个人的建议是:统一用误差比较。省得哪天不小心引入一个运算,把「安全」变成「不安全」。
六、知识体系图
下面这张图,帮你理清浮点数比较的核心逻辑:
七、总结
浮点数比较这个问题,说大不大,说小不小。但一旦踩中,排查起来非常痛苦。你想想看,一个 if 条件明明应该成立,程序就是不进去,这种 bug 最让人抓狂。
记住三条:
- 不要用 == 比较浮点数——除非你 100% 确定它在二进制里能精确表示。
- 用误差范围比较——
fabs(a - b) < EPSILON是标准做法。 - EPSILON 要选对——根据你的精度需求来,不是越小越好。
嗯,浮点数比较就讲到这里。下次写代码遇到浮点数判断,多留个心眼。
公众号:蓝海资料掘金营,微信 deep3321