go中直接用==比较float64通常错误,因浮点数是二进制近似表示,0.1+0.2≠0.3属正常;==执行严格比特比较,微小二进制差异即导致false;应改用math.abs(a-b)

Go 里直接用 == 比较两个 float64 基本上是错的
浮点数在内存中是二进制近似表示,0.1 + 0.2 不等于 0.3 是常态,不是 bug。Go 的 == 是严格比特比较,只要两个值的二进制表示稍有差异,结果就是 false,哪怕肉眼看不出差别。
常见错误现象:math.Abs(a - b) 看起来合理,但没考虑数量级——当 <code>a 和 b 是 1e10 级别时,1e-9 这个绝对误差阈值毫无意义。
- 用相对误差判断更稳妥:先取较大绝对值作为基准,再比差值占比
- 简单场景可用
math.Abs(a-b) - 注意
a和b都为 0 时,math.Max返回 0,会导致除零风险(实际这里不会除零,但逻辑需覆盖) - 标准库没提供现成函数,别指望
float64.Equal这种东西存在
用 math.Nextafter 判断“是否落在同一个浮点箱”
IEEE 754 规定每个浮点值都有前驱和后继,相邻可表示值之间的距离叫 ULP(Unit in the Last Place)。用 ULP 差距来衡量“接近程度”,比固定 epsilon 更符合浮点数本质。
使用场景:需要高鲁棒性比较,比如数值算法验证、测试断言、金融计算中的容差控制。
立即学习“go语言免费学习笔记(深入)”;
-
math.Nextafter(x, math.Inf(1))得到x的下一个更大浮点数 - ULP 差 =
int64(math.Abs(x-y)) / int64(math.Abs(math.Nextafter(x,0)-x))—— 实际不这么算,推荐用现成库如github.com/achilleas/ulp或手写ULPDistance函数 - 常见误用:对
NaN或无穷大调用Nextafter,会返回非预期值;务必先用math.IsNaN和math.IsInf过滤 - 性能影响小,但比纯绝对误差多几次函数调用,高频循环里要注意
测试中怎么写可靠的浮点断言(以 testify/assert 为例)
很多人直接写 assert.Equal(t, expected, actual),结果 CI 偶发失败。这不是测试不稳,是断言方式错了。
关键点:测试框架的浮点比较必须显式指定容差,且容差要匹配业务语义。
-
assert.InEpsilon(t, expected, actual, 1e-6)用相对误差,适合大多数科学计算 -
assert.InDelta(t, expected, actual, 0.01)用绝对误差,适合固定量纲场景(如温度 ±0.01℃) - 别混用:比如用
InDelta比较1e-15级别的值,0.01容差太大,永远通过 - 自定义错误信息里带上原始值:
assert.InEpsilonf(t, expected, actual, 1e-9, "failed at i=%d", i)
什么时候真该用 float64?还是换 int64 或 decimal?
精度问题本质是选型问题。不是所有“带小数”的场景都该用浮点。
容易踩的坑:把金额、配置比例、版本号小数部分全塞进 float64,后面 debug 花半天。
- 钱 → 用整数单位(分)或专用库如
shopspring/decimal;float64表示 0.1 元本身就是错的 - 比例(如 0.75 表示 75%)→ 若只做展示或粗略计算,
float64可接受;若参与累计(如税率叠加),必须转整数或 decimal - 物理模拟、机器学习权重 →
float64合理,但比较仍需 ULP 或相对误差 - 配置文件读出的数字默认是
float64(如 json 解析),如果业务要求精确,应在解析后立刻转int64或校验范围
最常被忽略的一点:浮点比较的“正确性”依赖于你对误差来源的理解。不是套个 1e-9 就万事大吉,得知道这个数是从哪儿来的——是算法截断误差?输入测量误差?还是单纯二进制表示限制?










