Go语言必知必会100问题-19 浮点数溢出问题

问题呈现

在Go语言中,有两种浮点数类型(虚数除外):float32和float64. 浮点数是用来解决整数不能表示小数的问题。我们需要知道浮点数算术运算是实数算术运算的近似,下面通过例子说明浮点数运算采用近似值的影响以及如何提高计算精度。

var n float32 = 1.0001
fmt.Println(n * n)

上面的程序,我们的预期结果可能是 1.0001 * 1.0001 = 1.00020001。然而,实际上在大多数的x86处理器上,运行结果为 1.0002。

原因分析

如何解释这种差异呢?我们先来理解浮点数运算规则。

以float64为例,在math.SmallestNonzeroFloat64(float64的最小值)到math.MaxFloat64(float64的最大值)区间内有无穷尽个实数值。但是float64是用64个bit位表示的,将无穷尽的实数一一映射到有限的64个bit上是无法实现的。必须采用近似值的方法,丢失一些精度信息。同理对于float32类型,也是这样。

Go语言中的浮点数遵循IEEE-754标准,用部分bit位表示尾数,另一部分bit位表示指数。尾数用来表示基本值,指数将与尾数进行相乘得到的结果为最终的数值。在单精度浮点类型(float32)中,用8个bit位表示指数,23个bit位表示尾数,还有1个bit位是符号位。在双精度浮点类型(float64)中,分别用11个和52个bit位表示指数和尾数,剩下的1个bit位表示符合。可以用下面的计算公式将浮点数转为十进制数。

sign * 2^exponent * mantissa

下图是数值1.0001(float32)在IEEE-754下的计算机表示。阶码由8个bit位构成:01111111, 而阶码=原码+偏置值, 8位的偏值为:2^(8-1)-1=127. 所以原码的值为0,即exponent为0. mantissa的值为1.000100016593933. 因此它的十进制数为: 1 × 2^0 × 1.000100016593933. 原本1.0001在计算机中的存储的实际值是1.000100016593933,所以缺少精度会影响存储值的准确性。

在这里插入图片描述

解决方法

通过上面的一个具体的例子了解了浮点数在计算机中存储的是近似值。那我们在开发程序的时候需要注意什么呢?第一个需要注意的是比较操作,使用 == 运算符比较两个浮点数可能会导致不准确。我们应该比较它们的差值,看差值是否在一个小的误差内。例如,用于测试的testify(https://github.com/stretchr/testify)库有一个InDelta函数来断言两个值是否在给定的delta范围内。第二个需要注意的是浮点数的结果取决于实际的处理器。大多数处理器都有一个浮点单元(FPU)来处理这种计算,不能保证在一台机器上执行的结果在另一台具有不同FPU的机器上相同。通过比较差值是否在一定的范围内可能是跨不同机器实现有效测试的解决方案。

经验一:用好三种特殊浮点数

Go语言中还有三种特殊的浮点数:正无穷大、负无穷大、NaN(Not-a-Number)。根据IEEE-754标准,NaN是唯一满足 f!=f的浮点数。下面是创建特殊浮点数的示例。

var a float64
positiveInf := 1 / a
negativeInf := -1 / a
nan := a / a
fmt.Println(positiveInf, negativeInf, nan)
+Inf -Inf NaN

我们可以使用math库中的math.IsInf检查浮点数是否为无穷大,以及使用math.IsNaN检查浮点数是否为NaN.

经验二:注意累积放大偏差

十进制数到浮点数的转换可能存在精度下降,这是由于转换导致的错误。此外,还要注意错误可以在一系列浮点运算中累积, 通过下面这个例子进行说明。f1和f2函数以不同的顺序执行相同的操作,在f1函数中,result先被初始化为float64类型的10000, 然后在循环中每次自增1.0001。相反,f2函数先进行自增操作,然后增加10000.

func f1(n int) float64 {
    result := 10_000.
    for i := 0; i < n; i++ {
        result += 1.0001
    }
    return result
}
 
func f2(n int) float64 {
    result := 0.
    for i := 0; i < n; i++ {
        result += 1.0001
    }
    return result + 10_000.
}

在x86处理器上执行上述计算,得到结果如下。可以看到,n越大,不精确性越大。f2的精度比f1要高。

nExact resultf1f2
1010010.000110010.00099999999310010.001
1k11000.111000.09999999929311000.099999999982
1m1.0101e+061.0100999999761417e+061.0100999999766762e+06

如果对浮点数进行乘法和除法运算,结果是什么样的呢?现在假设要执行下面的运算操作:

a * ( b + c )

作所周知,运用数学分配率,上面的结果和下面的是一样的。

a * b + a * c

现在通过程序进行验证以下,看看是否如上面的预期一样。代码如下:

a := 100000.001
b := 1.0001
c := 1.0002
 
fmt.Println(a * (b + c))
fmt.Println(a*b + a*c)

运行上述程序,得到的结果如:

200030.00200030004
200030.0020003

准确的结果应该是200030.002,所以第一种计算方法得到的精度最差。事实上,当执行操作涉及加法、减法、乘法和除法时,先进行乘法和除法运算,能够获得更好的精度。虽然,这可能会影响执行时间(第二种计算方法需要3步操作,第一种方法只需两步操作),但这是执行结果准确度和执行时间之间权衡的选择。

思考总结

Go语言中float32和float64在计算机中是一种近似值表示,因此,我们必须牢记下面的规则:

  • 当比较两个浮点数时,检查它们的差值是否在可接受的范围内,而不是直接 == 进行比较

  • 当执行加法或减法时,为了获得更好的精度,可以根据运算级进行分组

  • 为了提高准确性,如果一系列运算需要加法、减法、乘法或除法,先执行乘法和除法运算

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445684.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

⭐每天一道leetcode:83.删除排序链表中的重复元素(简单;链表遍历、删除经典题目)

⭐今日份题目 给定一个已排序的链表的头 head &#xff0c; 删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 示例1 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 示例2 输入&#xff1a;head [1,1,2,3,3] 输出&#xff1a;[1,2,3] …

【R语言爬虫实战】抓取省市级城市常务会议内容

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

enumerate函数的用法

enumerate() 函数是 Python 内置函数之一&#xff0c;用于同时返回可迭代对象的索引和对应的值。 它的语法结构如下&#xff1a; enumerate(iterable, start0) iterable: 表示一个可迭代的对象&#xff0c;如列表、元组、字符串等。start: 可选参数&#xff0c;表示索引起始…

02hadoop伪分布式搭建

3. 环境安装 3.1 安装方式 单机模式 只能启动MapReduce 伪分布式 能启动HDFS、MapReduce 和 YARN的大部分功能 完全分布式 能启动Hadoop的所有功能 3.2 安装JDK 3.2.1 JDK安装步骤 下载JDK安装包&#xff08;下载Linux系统的 .tar.gz 的安装包&#xff09; https://www…

网络协议常见问题

网络协议常见问题 OSI&#xff08;Open Systems Interconnection&#xff09;模型OSI 封装 TCP/IP协议栈IP数据报的报头TCP头格式UDP头格式TCP (3-way shake)三次握手建立连接&#xff1a;为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。每次建立TCP连接…

蓝桥杯递推与递归法|斐波那契数列|数字三角形|42点问题|数的计算|数的划分(C++)

递归是用来做dfs&#xff0c;是搜索算法的基础 递推是用来做dp部分&#xff0c;及部分其他算法&#xff0c;复杂度较低&#xff0c;不会出现爆栈问题递推法&#xff1a; 递推法是一种在数学和其他领域广泛应用的重要方法&#xff0c;它在计算机科学中被用作一种关键的数值求解…

自动化运维利器Ansible基础(环境部署)

Ansible 介绍及安装 1. 介绍 Ansible 是⼀个 IT ⾃动化⼯具。它能配置系统、部署软件、编 排更复杂的 IT 任务&#xff0c;如连续部署或零停机时间滚动更新。 Ansible ⽤ Python 编写&#xff0c;尽管市⾯上已经有很多可供选择的 配置管理解决⽅案&#xff08;例如 Salt、Pupp…

OpenAI GPT LLMs 高级提示词工程方法汇总

原文地址&#xff1a;An Introduction to Prompt Engineering for OpenAI GPT LLMs Github&#xff1a;Prompt-Engineering-Intro 2023 年 3 月 2 日 提示工程指南 | Prompt Engineering Guide Naive 提示词&#xff1a;带有提示的情感分类器 prompt Decide whether a T…

复合查询【MySQL】

文章目录 复合查询测试表 单表查询多表查询子查询单行子查询多行子查询IN 关键字ALL 关键字ANY 关键字 多列子查询 合并查询 复合查询 测试表 雇员信息表中包含三张表&#xff0c;分别是员工表&#xff08;emp&#xff09;、部门表&#xff08;dept&#xff09;和工资等级表&…

GEE:基于ERA5数据集(U和V风速分量)计算风速的幅值和风向

作者:CSDN @ _养乐多_ 本文将介绍使用Google Earth Engine (GEE)平台提供的API加载ERA5月度数据集,该数据集包含了从1979年至今的全球月度气象数据。然后,定义了一个数据计算函数,用于将U和V风速分量转换为风速的幅值和风向。 结果如下图所示, 文章目录 一、核心函数1…

基于单片机的语音存储与回放系统设计

目 录 摘 要 I Abstract II 引 言 1 1 控制系统设计 3 1.1 系统方案设计 3 1.2 系统工作原理 4 1.2.1 单片机的选择 4 1.2.2 语音芯片的选择 5 2 硬件电路设计 6 2.1 时钟电路 6 2.2 复位电路 6 2.3 显示电路 7 2.4 电源电路 7 2.5 按键模块电路 8 2.6 LM386功放电路 8 2.7 总…

基于深度学习YOLOv8+Pyqt5的抽烟吸烟检测识别系统(源码+跑通说明文件)

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;39抽烟 获取完整源码源文件4000张已标注的数据集配置说明文件 可有偿59yuan一对一远程操作跑通 效果展示 基于深度学YOLOv8PyQt5的抽烟吸烟检测识别系统&#xff08;完整源码跑通说明文件&#xff09; 各文件说明 模型评价…

Seurat 中的数据可视化方法

本文[1]将使用从 2,700 PBMC 教程计算的 Seurat 对象来演示 Seurat 中的可视化技术。您可以从 SeuratData[2] 下载此数据集。 SeuratData::InstallData("pbmc3k")library(Seurat)library(SeuratData)library(ggplot2)library(patchwork)pbmc3k.final <- LoadData(…

【机器学习300问】31、不平衡数据集如何进行机器学习?

一、什么是不平衡的数据集&#xff1f; &#xff08;1&#xff09;认识不平衡数据 假如你正在管理一个果园&#xff0c;这个果园里主要有两种水果——苹果和樱桃。如果苹果树有1000棵&#xff0c;而樱桃树只有10棵&#xff0c;那么在收集果园的果实时&#xff0c;你会得到大量…

RocketMQ架构详解

文章目录 概述RocketMQ架构rocketmq的工作流程Broker 高可用集群刷盘策略 概述 RocketMQ一个纯java、分布式、队列模型的开源消息中间件&#xff0c;前身是MetaQ&#xff0c;是阿里研发的一个队列模型的消息中间件&#xff0c;后开源给apache基金会成为了apache的顶级开源项目…

全栈的自我修养 ———— css中常用的布局方法flex和grid

在项目里面有两种常用的主要布局:flex和grid布局&#xff08;b站布局&#xff09;&#xff0c;今天分享给大家这两种的常用的简单方法&#xff01; 一、flex布局1、原图2、中心对齐3、主轴末尾或者开始对其4、互相间隔 二、grid布局1、基本效果2、加间隔3、放大某一个元素 一、…

Nginx请求转发和Rewrite的URL重写及重定向的功能实现移动端和PC端前端服务转发和重定向配置。

应用场景说明一 应用系统分pc端和微信小程序&#xff0c;移动端和pc端分别申请二级子域名&#xff0c;通过Nginx域名解析匹配&#xff0c;将web访问统一转发至对应的域名请求中。部分配置如下所示&#xff1a; 1、WEB访问统一入口域名解析转发配置&#xff0c;PC端和移动端根域…

【论文整理】自动驾驶场景中Collaborative Methods多智能体协同感知文章创新点整理

Collaborative Methods F-CooperV2VNetWhen2commDiscoNetAttFusionV2X-ViTCRCNetCoBERTWhere2commDouble-MCoCa3D 这篇文章主要想整理一下&#xff0c;根据时间顺序这些文章是怎么说明自己的创新点的&#xff0c;又是怎么说明自己的文章比别的文章优越的。显然似乎很多文章只是…

数据结构与算法:链式二叉树

上一篇文章我们结束了二叉树的顺序存储&#xff0c;本届内容我们来到二叉树的链式存储&#xff01; 链式二叉树 1.链式二叉树的遍历1.1二叉树的前序&#xff0c;中序&#xff0c;后序遍历1.2 三种遍历方法代码实现 2. 获取相关个数2.1获取节点个数2.2获取叶节点个数2.3 获取树的…

前端请求到 SpringMVC 的处理流程

1. 发起请求 客户端通过 HTTP 协议向服务器发起请求。 2. 前端控制器&#xff08;DispatcherServlet&#xff09; 这个请求会先到前端控制器 DispatcherServlet&#xff0c;它是整个流程的入口点&#xff0c;负责接收请求并将其分发给相应的处理器。 3. 处理器映射&#xf…