基于Go语言对浮点数进行比较的方法

 更新时间:2026年05月13日 08:21:15   作者:妙莎  
在 Go 语言(以及大多数遵循 IEEE 754 标准的编程语言)中,‌差值容限(Epsilon) ‌ 是指在进行浮点数比较时,允许两个数值之间存在的最大微小误差范围,本文给大家介绍了基于Go语言对浮点数进行比较的方法,需要的朋友可以参考下

差值容限

Go 语言(以及大多数遵循 IEEE 754 标准的编程语言)中,‌差值容限(Epsilon) ‌ 是指在进行浮点数比较时,允许两个数值之间存在的最大微小误差范围。

一、为什么需要差值容限?

计算机使用 二进制 存储浮点数(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)。如果两个浮点数 AA 和 BB 的差的绝对值小于这个容限,我们就认为它们在 “工程意义”“业务意义” 上是相等的。

公式如下:
∣A−B∣< ϵ
其中 ∣A−B∣‌ 是 两个数的绝对差值ϵ‌ 是你设定的 容忍误差阈值

Go 语言如何实现差值容限?

方法一:固定容限(适用于已知数据范围较小的场景)

定义一个常量 epsilon,通常设为 1e-9(对于 float64)或 1e-6(对于 float32)。

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 太严格,正常的舍入误差可能超过它。
  • 对于 小数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)

如何选择容限值?

数据类型推荐默认容限说明
float321e-6精度约为 7 位十进制数字
float641e-9 ~ 1e-15精度约为 15-17 位十进制数字;
一般业务用 1e-9 足够;
科学计算可能需要更小。

注意:

  • 金融/货币场景‌:‌不要‌使用浮点数比较,即使加了容限也不推荐。应将金额转换为整数(如 “分” )进行精确比较,或使用 github.com/shopspring/decimal 等高精度库。
  • 图形/物理引擎‌:通常使用固定容限(如 1e-5),因为坐标值范围通常可控。

总结

Go 语言中比较浮点数时:

  • 严禁‌ 直接使用 ==
  • 常用做法‌:计算两数之差的绝对值 math.Abs(a - b)
  • 判断标准‌:检查该差值是否小于你定义的‌ 差值容限(epsilon) ‌。
  • 进阶做法‌:若数值范围差异巨大,使用 相对容限 比较。

到此这篇关于基于Go语言对浮点数进行比较的方法的文章就介绍到这了,更多相关Go浮点数进行比较内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解如何使用go-acme/lego实现自动签发证书

    详解如何使用go-acme/lego实现自动签发证书

    这篇文章主要为大家详细介绍了如何使用 go-acme/lego 的客户端或库完成证书的自动签发,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • Golang学习之内存逃逸分析

    Golang学习之内存逃逸分析

    内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上。本文将带大家分析一下Golang中的内存逃逸,需要的可以了解一下
    2023-01-01
  • Golang标准库和外部库的性能比较

    Golang标准库和外部库的性能比较

    这篇文章主要介绍Golang标准库和外部库的性能比较,下面文章讲围绕这两点展开内容,感兴趣的小伙伴可以参考一下
    2021-10-10
  • go singleflight缓存雪崩源码分析与应用

    go singleflight缓存雪崩源码分析与应用

    这篇文章主要为大家介绍了go singleflight缓存雪崩源码分析与应用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • GO语言基础之数组

    GO语言基础之数组

    或许您是从其他语言转到GO语言这边的,那麽在其他语言的影响下您可能会不太适应GO语言的数组,因为GO语言把数组给拆分成了array,slice和map,需要的朋友可以参考下
    2015-01-01
  • golang给函数参数设置默认值的几种方式小结(函数参数默认值

    golang给函数参数设置默认值的几种方式小结(函数参数默认值

    在日常开发中我们有时候需要使用默认设置,下面这篇文章主要给大家介绍了关于golang给函数参数设置默认值的几种方式小结的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • golang如何自定义json序列化应用详解

    golang如何自定义json序列化应用详解

    son格式可以算我们日常最常用的序列化格式之一了,下面这篇文章主要给大家介绍了关于golang如何自定义json序列化应用的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧
    2018-08-08
  • Go语言时间管理利器之深入解析time模块的实战技巧

    Go语言时间管理利器之深入解析time模块的实战技巧

    本文深入解析了Go语言标准库中的time模块,揭示了其高效用法和实用技巧,通过学习time模块的三大核心类型(Time、Duration、Timer/Ticker)以及高频使用场景,开发者可以更好地处理时间相关的任务,感兴趣的朋友一起看看吧
    2025-03-03
  • 一个Pod调度失败后重新触发调度的所有情况分析

    一个Pod调度失败后重新触发调度的所有情况分析

    这篇文章主要为大家介绍了一个Pod调度失败后重新触发调度的所有情况分析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • go-redis Pipeline与事务的实现示例

    go-redis Pipeline与事务的实现示例

    本文介绍了Go-Redis v9中Pipeline、事务和Watch机制的使用方法与优化实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-11-11

最新评论