浮点数比较:为什么不能直接用 == 比较浮点数?

这个问题,我几乎每次面试新人都会问。十个人里有八个会答「因为精度问题」,但再追问一句「具体怎么个精度问题」,能说清楚的就很少了。

我自己刚入行那会儿也踩过这个坑。有一次调试一个电机控制程序,明明计算出来的速度应该是 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-61e-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() 函数,不要自己比较。

但说实话,我个人的建议是:统一用误差比较。省得哪天不小心引入一个运算,把「安全」变成「不安全」。

六、知识体系图

下面这张图,帮你理清浮点数比较的核心逻辑:

浮点数比较知识体系 浮点数比较 ❌ 直接使用 == ✅ 使用误差范围 二进制近似误差 精度截断不一致 fabs(a - b) < EPS 根据场景选 EPSILON 核心原则:比接近,不比相等

七、总结

浮点数比较这个问题,说大不大,说小不小。但一旦踩中,排查起来非常痛苦。你想想看,一个 if 条件明明应该成立,程序就是不进去,这种 bug 最让人抓狂。

记住三条:

  1. 不要用 == 比较浮点数——除非你 100% 确定它在二进制里能精确表示。
  2. 用误差范围比较——fabs(a - b) < EPSILON 是标准做法。
  3. EPSILON 要选对——根据你的精度需求来,不是越小越好。

嗯,浮点数比较就讲到这里。下次写代码遇到浮点数判断,多留个心眼。


公众号:蓝海资料掘金营,微信 deep3321