Golang浮点数精度问题分析_比较两个浮点数是否相等

6次阅读

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

Golang浮点数精度问题分析_比较两个浮点数是否相等

Go 里直接用 == 比较两个 float64 基本上是错的

浮点数在内存中是二进制近似表示,0.1 + 0.2 不等于 0.3 是常态,不是 bug。Go 的 == 是严格比特比较,只要两个值的二进制表示稍有差异,结果就是 false,哪怕肉眼看不出差别。

常见错误现象:math.Abs(a - b) 看起来合理,但没考虑数量级——当 <code>ab1e10 级别时,1e-9 这个绝对误差阈值毫无意义。

  • 用相对误差判断更稳妥:先取较大绝对值作为基准,再比差值占比
  • 简单场景可用 math.Abs(a-b)
  • 注意 ab 都为 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.IsNaNmath.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?还是换 int64decimal

精度问题本质是选型问题。不是所有“带小数”的场景都该用浮点。

容易踩的坑:把金额、配置比例、版本号小数部分全塞进 float64,后面 debug 花半天。

  • 钱 → 用整数单位(分)或专用库如 shopspring/decimalfloat64 表示 0.1 元本身就是错的
  • 比例(如 0.75 表示 75%)→ 若只做展示或粗略计算,float64 可接受;若参与累计(如税率叠加),必须转整数或 decimal
  • 物理模拟、机器学习权重 → float64 合理,但比较仍需 ULP 或相对误差
  • 配置文件读出的数字默认是 float64(如 json 解析),如果业务要求精确,应在解析后立刻转 int64 或校验范围

最常被忽略的一点:浮点比较的“正确性”依赖于你对误差来源的理解。不是套个 1e-9 就万事大吉,得知道这个数是从哪儿来的——是算法截断误差?输入测量误差?还是单纯二进制表示限制?

text=ZqhQzanResources