当前位置:

首页 > 编程开发 > 基于Go语言对浮点数进行比较的方法

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

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

差值容限

聊到Go语言里的浮点数比较,就绕不开一个概念——差值容限(Epsilon)。简单来说,它就是给两个浮点数比较时,允许存在的一个微小误差范围。在遵循IEEE 754标准的编程语言里,这几乎是标配了。

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

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

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

公式如下:

|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)

如何选择容限值?

数据类型推荐默认容限说明
float321e-6精度约为 7 位十进制数字
float641e-9 ~ 1e-15精度约为 15-17 位十进制数字;

一般业务用 1e-9 足够;

科学计算可能需要更小。

注意:

  • 金融/货币场景不要使用浮点数比较,哪怕加了容限也不推荐。应该把金额转换成整数(比如“分”)来做精确比较,或者使用 github.com/shopspring/decimal 这类高精度库。
  • 图形/物理引擎:通常用固定容限(比如 1e-5),因为坐标范围一般可控。

总结

Go 语言里比较浮点数,记住这几点:

  • 直接拿 == 去比。
  • 最常用的做法:算一下 math.Abs(a - b),看看这个差的绝对值,是不是小于你定义的 差值容限(epsilon)
  • 更进阶的做法:如果数值范围差异很大,用 相对容限 来比较,会更稳妥。
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

c语言函数递归 怎么选?常见方案对比分析
c语言函数递归 怎么选?常见方案对比分析

递归函数的基本概念与适用场景在C语言编程中,递归是一种函数调用自身的编程技巧。它并非适用于所有问题,但在处理某些具有自相似结构的问题时,能提供极其清晰和优雅的解决方案。递归的核心思想是将一个大规模问题分解为一个或多个同类型但规模更小的子问题,直到子问题简单到可以直接求解。典型的适用场景包括树形结构的

Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解
Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解

理解内存管理的基石在Objective-C的编程世界中,内存管理是开发者必须掌握的核心技能之一。它直接关系到应用的性能、稳定性与资源利用效率。与一些采用自动垃圾回收机制的语言不同,Objective-C在很长一段时间里,依赖一套基于引用计数的、需要开发者部分介入的管理规则。这套规则的核心思想是明确的

如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏
如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏

理解 dealloc 的角色与时机在 iOS 应用开发中,内存管理是保障应用性能与稳定性的基石。dealloc 方法是 Objective-C 中对象生命周期结束时的关键回调,它标志着对象即将被系统回收内存。正确理解其触发时机至关重要:当一个对象的引用计数降为零时,运行时系统会自动调用该对象的 de

深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制
深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制

内存管理的基石在Objective-C的世界里,内存管理是开发者必须掌握的核心技能之一。作为一门在手动引用计数(MRC)时代诞生的语言,Objective-C要求程序员对对象的生命周期有清晰的认识。dealloc方法正是这一生命周期中至关重要的终点站。它是一个实例方法,当对象的引用计数降为零时,系统

理解 native2ascii:Java 国际化开发中的字符编码工具
理解 native2ascii:Java 国际化开发中的字符编码工具

native2ascii 工具的基本定位在Ja va应用程序的国际化与本地化开发过程中,处理非拉丁字符集是一个常见且关键的环节。Ja va内部使用Unicode字符集来统一表示全球各种语言的文字,但其属性文件(.properties)在历史上要求使用ASCII编码,或者更准确地说,要求非ASCII字

如何使用 native2ascii 转换中文字符为 Unicode 转义序列
如何使用 native2ascii 转换中文字符为 Unicode 转义序列

理解 native2ascii 工具的基本用途在软件开发,特别是涉及国际化处理的场景中,开发者常常需要处理不同编码的文本资源。native2ascii 是 Ja va 开发工具包(JDK)中提供的一个命令行实用程序,其主要功能是将包含本地字符编码(非ASCII字符)的文件,转换为包含 Unicode

Java native2ascii 命令详解:解决属性文件乱码问题
Java native2ascii 命令详解:解决属性文件乱码问题

native2ascii 命令的由来与作用在Ja va开发中,处理国际化资源文件是一个常见需求。资源文件通常以.properties格式存储,用于支持多语言界面。然而,Ja va属性文件默认采用ISO-8859-1字符集编码,这导致了一个直接的问题:当文件中包含非拉丁字符(如中文、日文、韩文等)时,

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。