基于Go语言对浮点数进行比较的方法
差值容限 聊到Go语言里的浮点数比较,就绕不开一个概念——差值容限(Epsilon)。简单来说,它就是给两个浮点数比较时,允许存在的一个微小误差范围。在遵循IEEE 754标准的编程语言里,这几乎是标配了。 一、为什么需要差值容限? 根子在于计算机用二进制来存储浮点数(float32 或 float
差值容限
聊到Go语言里的浮点数比较,就绕不开一个概念——差值容限(Epsilon)。简单来说,它就是给两个浮点数比较时,允许存在的一个微小误差范围。在遵循IEEE 754标准的编程语言里,这几乎是标配了。

一、为什么需要差值容限?
根子在于计算机用二进制来存储浮点数(float32 或 float64)。像0.1、0.2这样的十进制小数,在二进制里是无限循环的,根本没法精确存下来,只能存个近似值。这就导致每次运算,结果都会带点微小的精度丢失。
举个最经典的例子:
package main
import "fmt"
func main() {
f1 := 0.1
f2 := 0.2
sum := f1 + f2
// 数学上 0.1 + 0.2 == 0.3,但在计算机中:
fmt.Printf("%.20f\n", sum)
fmt.Println(sum == 0.3)
}
0.30000000000000004441 false
你看,直接用 == 比较,结果就是 false。因为 sum 实际上比 0.3 大了一丁点。那么,怎么解决这个问题呢?答案就是引入一个叫 差值容限 的东西。
二、差值容限的含义
它的核心逻辑很简单:
差值容限 是一个极小的正数,通常记作 ϵ 或 epsilon。如果两个浮点数 A 和 B 的差的绝对值,小于这个小小的容限,那我们就认为它们在“工程意义”或“业务意义”上是相等的。
公式如下:
|A - B| < ϵ
意思就是:两个数的距离,只要不超过你设定的容忍阈值,就视作相等。
Go 语言如何实现差值容限?
方法一:固定容限(适用于已知数据范围较小的场景)
这是最直接的做法。定义一个常量 epsilon,比如对于 float64 通常设为 1e-9,对于 float32 则设为 1e-6。
package main
import (
"fmt"
"math"
)
// 定义容限,根据业务精度需求调整
const epsilon = 1e-9
// IsEqual 判断两个 float64 是否 "近似相等"
func IsEqual(f1, f2 float64) bool {
return math.Abs(f1-f2) < epsilon
}
func main() {
f1 := 0.1 + 0.2
f2 := 0.3
if IsEqual(f1, f2) {
fmt.Println("a 和 b 近似相等")
} else {
fmt.Println("a 和 b 不相等")
}
}
a 和 b 近似相等
方法二:相对容限(适用于数据跨度大的场景,更严谨)
如果参与比较的数字范围跨度很大,比如一个是 1e20,另一个是 1e-20,那固定容限就不太灵了。
- 对于 大数,
1e-9可能太严格,正常的舍入误差都可能超过它。 - 对于 小数,
1e-9又太宽松,可能导致不相关的数被判为相等。
这时候就需要 相对误差,让容限随数值大小动态变化:
package main
import (
"fmt"
"math"
)
// IsApproximatelyEqual 使用相对容限比较
func IsApproximatelyEqual(f1, f2, epsilon float64) bool {
// 处理特殊情况:如果两个数都非常接近0,直接比较绝对差
if math.Abs(f1) < epsilon && math.Abs(f2) < epsilon {
return math.Abs(f1-f2) < epsilon
}
// 相对误差公式: |f1 - f2| / max(|f1|, |f2|) < epsilon 等价于: |f1 - f2| < max(|f1|, |f2|) * epsilon
// 分母取两者中较大的绝对值,避免除以零或过小值
largest := math.Max(math.Abs(f1), math.Abs(f2))
return math.Abs(f1-f2) <= largest*epsilon
}
func main() {
// 场景:大数运算
// 在 float64 中,1e15 的精度大约是 1e-1 (即小数点后 1 位左右是可靠的)
// 任何更小的尾数变化都可能因为舍入而丢失或产生较大绝对误差
// 让我们构造一个更典型的 "计算后应相等但存在误差" 的例子
// 比如:(1e15 + 0.1) - 1e15 理论上等于 0.1
// 但实际上,由于 1e15 太大,0.1 加上去可能被舍入掉,或者产生微小偏差
f1 := 1e15
f2 := 1e15 + 0.1
f3 := f2 - 1e15
fmt.Printf("原始值 f1: %.20f\n", f1)
fmt.Printf("中间值 f2 (f1 + 0.1): %.20f\n", f2)
fmt.Printf("计算值 f3 (f2 - f1): %.20f\n", f3)
fmt.Printf("期望值: 0.1\n")
fmt.Printf("绝对差值 |f3 - 0.1|: %.20e\n", math.Abs(f3-0.1))
epsilon := 1e-9
fmt.Println("\n--- 比较 f3 和 0.1 ---")
relativeResult := IsApproximatelyEqual(f3, 0.1, epsilon)
fmt.Printf("相对容限 (%.0e): %v\n", epsilon, relativeResult)
if relativeResult {
fmt.Println("-> 成功原因:虽然绝对差值大,但相对于数值大小,误差在允许范围内")
}
// 另一个更极端的例子:两个非常大的数,它们只差一点点,但绝对差值很大
fmt.Println("\n--- 极端大数比较 ---")
x := 1e20
y := 1e20 + 1e10 // 相差 100亿,相对于 1e20 来说很小
fmt.Printf("x: %.2e\n", x)
fmt.Printf("y: %.2e\n", y)
fmt.Printf("绝对差值: %.2e\n", math.Abs(x-y))
// 相对容限:差值 1e10 / 最大值 1e20 = 1e-10,小于 1e-9,所以成功
fmt.Printf("相对容限 (%.0e) 比较 x 和 y: %v (成功,因为相对误差仅为 1e-10)\n", epsilon, IsApproximatelyEqual(x, y, epsilon))
}
原始值 f1: 1000000000000000.00000000000000000000 中间值 f2 (f1 + 0.1): 1000000000000000.12500000000000000000 计算值 f3 (f2 - f1): 0.12500000000000000000 期望值: 0.1 绝对差值 |f3 - 0.1|: 2.49999999999999944489e-02 --- 比较 f3 和 0.1 --- 相对容限 (1e-09): false --- 极端大数比较 --- x: 1.00e+20 y: 1.00e+20 绝对差值: 1.00e+10 相对容限 (1e-09) 比较 x 和 y: true (成功,因为相对误差仅为 1e-10)
如何选择容限值?
| 数据类型 | 推荐默认容限 | 说明 |
|---|---|---|
float32 | 1e-6 | 精度约为 7 位十进制数字 |
float64 | 1e-9 ~ 1e-15 | 精度约为 15-17 位十进制数字; 一般业务用 1e-9 足够; 科学计算可能需要更小。 |
注意:
- 金融/货币场景:不要使用浮点数比较,哪怕加了容限也不推荐。应该把金额转换成整数(比如“分”)来做精确比较,或者使用
github.com/shopspring/decimal这类高精度库。 - 图形/物理引擎:通常用固定容限(比如 1e-5),因为坐标范围一般可控。
总结
在 Go 语言里比较浮点数,记住这几点:
- 别 直接拿
==去比。 - 最常用的做法:算一下
math.Abs(a - b),看看这个差的绝对值,是不是小于你定义的 差值容限(epsilon)。 - 更进阶的做法:如果数值范围差异很大,用 相对容限 来比较,会更稳妥。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















