必须用误差容限(epsilon)或高精度类型,因为float64遵循IEEE 754标准,十进制小数如0.1无法精确表示为有限位二进制,导致0.1+0.2实际存储为0.30000000000000004,与0.3字面量不等;该误差源于存储截断,非计算错误,故==几乎总是不可靠。

float64 比较不能用 ==,必须用误差容限(epsilon)或高精度类型,这是浮点数二进制表示的硬性限制,不是 Go 特有,也绕不开。
为什么 == 在 float64 上几乎总是错的
因为 0.1 + 0.2 在内存中实际是 0.30000000000000004,而字面量 0.3 是另一个近似值;两者在 IEEE 754 下不相等。这不是计算错误,是存储方式决定的——十进制小数转二进制必然截断,误差从初始化那一刻就存在。
常见翻车现场:
-
math.Pi * 5 * 5 == 78.53981633974483→false(哪怕你用fmt.Printf("%.15f")看起来一样) - 循环累加
0.1十次后和1.0比较 → 大概率false - 用
==判断计算结果是否“归零”(如物理模拟中的速度衰减)→ 常见死循环
math.Abs(a - b) 怎么选 epsilon
固定 epsilon = 1e-9 看似简单,但会误伤大数比较(比如 1e10 和 1e10+1 相差 1,但 1e-9 容忍不了),也会放过小数精度问题(比如 1e-15 级别的误差本该被发现)。
立即学习“go语言免费学习笔记(深入)”;
更稳妥的做法是按数量级缩放:
- 对绝对值
的数,用固定容限:比如 <code>1e-12 - 对较大数,改用相对误差:
math.Abs(a-b) - 生产代码推荐组合式:
math.Abs(a-b) (即绝对+相对混合)
别写 if a == b { ... },一律替换成封装好的 floatEqual(a, b, 1e-12) 函数。
需要精确相等?换类型,别硬扛
如果你的业务逻辑真依赖“完全相等”,比如金融对账、ID 校验、配置一致性判断,float64 就不该出现在那里。
- 金额、汇率、百分比 → 用
github.com/shopspring/decimal,初始化走decimal.NewFromFloat(x).Round(n),比较用.Equal() - 高精度中间计算 → 用
math/big.Float,但注意:必须SetPrec()后再SetString("x.x"),不能传float64字面量 - 整数倍小数(如角度、时间戳毫秒)→ 改用
int64存“最小单位”,比如存毫秒而非秒,避免所有浮点运算
强行在 float64 上做“精确比较”,等于在流沙上盖楼——越调越累,越压越歪。
日志和调试时怎么看出真值
打印时用 fmt.Printf("%.17g", x) 而不是 %.2f 或默认输出,因为 g 动词会自动选最短无损表示,且最多显示 17 位有效数字(float64 的理论极限),能暴露隐藏误差。
-
fmt.Printf("%.17g", 0.1+0.2)→0.30000000000000004 -
fmt.Printf("%.17g", math.Pi)→3.1415926535897931(这才是它在内存里的样子)
别信 fmt.Println(x) 的输出——它只显示约 6 位小数,掩盖了所有问题。
真正难的不是“怎么比”,而是想清楚:这个比较语义上到底需不需要精确?如果只是“视觉一致”或“业务可接受”,用容限就够了;如果涉及资金、状态跃迁、幂等校验,那从输入源头就得把 float64 换掉。


















