程序员必看:浮点数精度问题全解析

原创 typedef 2024-11-25 08:21

目录

  • 0、导读
  • 1、引言
  • 2、浮点数存储格式
  • 3、转换流程
    • 3.1、将整数部分转换为二进制
    • 3.2、将小数部分转为二进制
    • 3.3、规范化
    • 3.4、调整阶码
    • 3.5、尾数舍入
    • 3.6、组三元素
  • 4、单/双精度浮点数比较
    • 4.1、存储格式
    • 4.2、精度
    • 4.3、浮点数范围
    • 4.4、浮点数比较
  • 5、阶码相关问题探索
    • 5.1、什么是移码
    • 5.2、如何计算移码
    • 5.3、为什么要用移码表示
  • 6、指数e
    • 6.1、指数范围
    • 6.2、特殊值
  • 7、文中问题解答
  • 8、参考链接
  • 9、总结

0、导读

这篇文章主要探讨了浮点数在计算机中的表示、存储和精度问题。通过详细的解释和示例,您将了解浮点数误差的根源。文章内容较多,大约3700余字,阅读时间约为10分钟,建议先收藏,待有空时再细细品读。

1、引言

0.1 + 0.2 为什么不等于 0.3 ?

当被问及浮点数为何存在误差时,你将如何回答?

没看完这篇文章之前你可能会回答:"哼,反正我就知道有误差..."

阅读完这篇文章后,你将能够更准确地回答这类问题,让我们开始这段学习之旅吧!

2、浮点数存储格式

浮点型在内存中的存储不是像整形那样直接存储的,而是用一种二进制的科学计数法来表示的,具体的数学表达式为

V = (-1) s × M × 2 e

其中,e = E - 127

在计算机科学领域,IEEE 754 是一种标准,用于定义浮点数的表示方法,浮点型数据的存储格式如下

请务必记住,尾数存储用原码,阶码存储用移码

  • S(符号位):0代表正数,1代表负数。
  • E(阶码):指数字段需要同时表示正指数和负指数。为了得到存储的指数,在实际指数上加一个偏置,其中e=E-127
  • M(尾数):一个规范化尾数就是小数点左边只有一个1,然后是小数点后面的尾数部分。

注意本文后续使用的e表示科学计数法中的指数部分,E表示存储格式中的阶码,默认的对象都指单精度的浮点数。

3、转换流程

接下来我选择了一个恋爱脑的数字,将1314.520转换到32位单精度IEEE 754二进制浮点表示标准。

3.1、将整数部分转换为二进制

将整数部分反复除以2,并记录每次的余数,直到商为0为止。

division = quotient + remainder;
1314 ÷ 2 = 657 + 0;
657  ÷ 2 = 328 + 1;
328  ÷ 2 = 164 + 0;
164  ÷ 2 = 82  + 0;
82   ÷ 2 = 41  + 0;
41   ÷ 2 = 20  + 1;
20   ÷ 2 = 10  + 0;
10   ÷ 2 = 5   + 0;
5    ÷ 2 = 2   + 1;
2    ÷ 2 = 1   + 0;
1    ÷ 2 = 0   + 1;

从上面构造的列表的底部开始取所有余数,即为整数部分的二进制表示。131410=101 0010 00102

3.2、将小数部分转为二进制

将小数部分不断乘以2,并记录每次的整数部分,直到小数部分为0或达到所需的精度为止

#) multiplying = integer + fractional part;
1) 0.52 × 2 = 1 + 0.04;
2) 0.04 × 2 = 0 + 0.08;
3) 0.08 × 2 = 0 + 0.16;
4) 0.16 × 2 = 0 + 0.32;
5) 0.32 × 2 = 0 + 0.64;
6) 0.64 × 2 = 1 + 0.28;
7) 0.28 × 2 = 0 + 0.56;
8) 0.56 × 2 = 1 + 0.12;
9) 0.12 × 2 = 0 + 0.24;
10) 0.24 × 2 = 0 + 0.48;
11) 0.48 × 2 = 0 + 0.96;
12) 0.96 × 2 = 1 + 0.92;
13) 0.92 × 2 = 1 + 0.84;
14) 0.84 × 2 = 1 + 0.68;
15) 0.68 × 2 = 1 + 0.36;
16) 0.36 × 2 = 0 + 0.72;
17) 0.72 × 2 = 1 + 0.44;
18) 0.44 × 2 = 0 + 0.88;
19) 0.88 × 2 = 1 + 0.76;
20) 0.76 × 2 = 1 + 0.52;
21) 0.52 × 2 = 1 + 0.04;
22) 0.04 × 2 = 0 + 0.08;
23) 0.08 × 2 = 0 + 0.16;
24) 0.16 × 2 = 0 + 0.32;

虽然我们没有得到任何等于0的小数部分,但是我们有足够的迭代(超过尾数限制)。

从顶部开始依次取乘法运算的所有整数部分,即为小数部分的二进制:0.5210=0.1000 0101 0001 1110 1011 10002

3.3、规范化

前面得出了整数以及小数部分的二进制表示,合并以后即:

1314.5210= 101 0010 0010.1000 0101 0001 1110 1011 10002

将小数点向左移动 10 位,使其左边只剩下一位非零的数字

1314.5210= 101 0010 0010.1000 0101 0001 1110 1011 10002= 101 0010 0010.1000 0101 0001 1110 1011 10002 ×2 0= 1.0100 1000 1010 0001 0100 0111 1010 1110 002 ×2 10

再回顾一下浮点数的数学表达式 V = (-1) s × M × 2 e 由此可知

s = 0
M = 1.0100 1000 1010 0001 0100 0111 1010 1110 00
e = 10

3.4、调整阶码

根据规范化得知指数 e = 10,又根据公式 e = E - 127 可得知道 E=137,所以八位阶码的二进制表示如下所示:

E = 13710 = 1000 10012

3.5、尾数舍入

由第三步规范化得出的尾数M有34位,但是存储格式中尾数只有23位,下面划线的是多出的部分,所以需要对尾数按照一定的方式进行四舍五入。

M = 1. 0100 1000 1010 0001 0100 011 1 1010 1110 00

一共有四种舍入方式,

  • 向偶数舍入,就近舍入(默认)。
  • 朝0舍入:即朝数轴零点方向舍入,即直接截尾。
  • 朝正无穷舍入:对正数而言,只要多余位不全为0则向最低有效位进1;负数则直接截尾。
  • 朝负无穷舍入:对负数而言,向最低有效位进1;正数若多余位不全部为0则简单截尾。

向偶数舍入,简单理解就要让尾数的最后一位为0,让其保持偶数,能够被2整除。当尾数的最低位为0时,已经是属于偶数了,无需处理。当尾数最低位为1时,需要加1,使其保持偶数。

因为本例计算出尾数的最后一位为1,按照就近舍入(向偶舍入)原则需要加1使其保持偶数。

所以经过调整后的M为

M = 0100 1000 1010 0001 0100 011 + 1
M = 0100 1000 1010 0001 0100 100

3.6、组三元素

根据前面的步骤可以得知

s = 0
E = 1000 1001 2
M = 0100 1000 1010 0001 0100 100 2

1324.5210 = 0-1000 1001-0100 1000 1010 0001 0100 1002

我们去一个转换网站上验证一下转换结果,网站链接放在文章末尾了。

floatConverterIEEE754

可以看到,跟我们转换的结果是相同的,说明网站转换也是选择向偶数舍入的。

4、单/双精度浮点数比较

4.1、存储格式

类型符号位指数长度(Bit)尾数长度(Bit)
float1823
double11152

4.2、精度

浮点数的精度是由尾数的位数来决定的。

对于float型浮点数,尾数部分23位,换算成十进制就是 2^23=8388608,所以十进制精度只有6 ~ 7位;

这里的数字6和7可能会引起疑问,如何理解它们呢?

由于浮点数尾数的舍入问题,最后一位可能存在舍入误差,因此不完全准确。因此,可以准确表示的是后六位,而第七位则可能含有误差。

对于double型浮点数,尾数部分52位,换算成十进制就是 2^52 = 4503599627370496,所以十进制精度只有15 ~ 16位

类型有效位字节数
float6 - 74
double15 - 168

4.3、浮点数范围

类型最小值最大值
float1.175494351 E - 383.402823466 E + 38
double2.2250738585072014 E - 3081.7976931348623158 E + 308

4.4、浮点数比较

浮点数的比较通常用两数之差的绝对值小于一个自定义的数值时,代表两者相等,如下所示:

/**
 *Author:(公众号:typedef)
 */

#define FLOAT_EPSILON (0.000001) //Define your own tolerance
#define FloatIsEqual(a, b) ((fabs((a)-(b)))<(FLOAT_EPSILON))

另外一种方法是将浮点数同时放大一个倍数,然后转成整数之间的比较,比如同时放大10000倍等。

5、阶码相关问题探索

首先阶码E是用移码表示的,那么问题来了,什么叫移码?移码怎么计算?移码的含义是?浮点数为什么要用移码表示?

在解答这些知识点时,我们需要下面两点需要达成一致

  • 阶码使用的是非标准移码
  • 阶码是一个无符号的整数

5.1、什么是移码

移码是补码表示中最高符号位取反的结果。举个例子,上面计算1314.52时,指数是为10的。

+1010 = 0000 10102(真值)

原码:0000 1010
反码:0000 1010
补码:0000 1010
移码:1000 1010

所以10对应标准的移码 1000 1010

5.2、如何计算移码

注意浮点数中移码的计算是非标准的,仅偏移2n-1-1=127。所以移码的计算公式如下所示,其中n为阶码的位数:

E = e + 2 n-1 - 1
E = e + 127

所以10对应的移码为137。

5.3、为什么要用移码表示

它通过将数值加上一个固定的偏移量,使得原本可能是负数的数值变为非负数,从而简化了计算机中有符号数的表示和比较操作。使得计算机能够直接使用整数运算来比较浮点数的大小

6、指数e

6.1、指数范围

浮点数指数部分的实际取值范围是 [-2(e-1)+2, 2(e-1)-1],其中 e 为指数所占位数。32位浮点数,指数占8位,实际取值范围是 [-126, 127]。

-127用作表示0,128 用作表示无穷大和 NaN。NaN 是 "Not a Number" 的缩写,中文意思是“非数字”,通常用于表示一个未定义或不可表示的值。

换言之,8位阶码的表示范围是[0, 255],其中0和255用于表示特殊值。因此,根据公式推导,指数e的实际取值范围是[-126, 127]。

6.2、特殊值

形式指数(e)阶码(E)小数部分
-12700
无穷1282e-1 = 2550
NaN(非数)1282e-1 = 255非0

7、文中问题解答

此时再来回答文中引言提出的问题, 0.1 + 0.2 为什么不等于 0.3 ?

/**
 * Author:(公众号:typedef)
 */

#include 

int main() {
  double a = 0.1 + 0.2;
  printf("%.17f", a);
}

输出为0.30000000000000004,由于在尾数舍入时会带来一定的误差,所以并不完全相等。

当在被问及浮点数为何存在误差时,你将如何回答?欢迎文章留言说出你的看法。

如果不从技术的角度回答这个问题,可以这样回答:整数是离散的,有限的并能够被计算机表示的,小数部分是连续的,包含无穷多的数,数量之多是无法被计算机存储的,只能存储计算机能够表示的最接近这个数值的小数部分,所以可能会不相等。

8、参考链接

  • https://www.cnblogs.com/gyunf/p/12816817.html
  • https://www.h-schmidt.net/FloatConverter/IEEE754.html
  • https://zh.wikipedia.org/wiki/IEEE_754
  • https://docs.pingcode.com/ask/304021.html

9、总结

本篇文章深入分析了浮点数的存储格式到转换流程,再到指数e以及阶码E的探索,大家应该对浮点数有了更全面的理解。

码字不易,如果您觉得有收获,欢迎点赞、转发,加关注!

END

点赞、转发加关注,一键三连,好运年年

关注公众号后台回复数字688或668可获取嵌入式相关资料

往期推荐

C语言编程新手:如何判断结构体(struct)相等?

避免内存陷阱:掌握memcpy和memmove的正确用法

揭秘难以复现Bug的解决之道:堆栈分析实战

加个变量,程序崩了

typedef 主要用于记录个人学习、总结、分享的一个平台。 教学相长,共同进步。同时并建立技术交流群,欢迎加入。期待您的关注。
评论
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 85浏览
  • 影像质量应用于多个不同领域,无论是在娱乐、医疗或工业应用中,高质量的影像都是决策的关键基础。清晰的影像不仅能提升观看体验,还能保证关键细节的准确传达,例如:在医学影像中,它对诊断结果有着直接的影响!不仅如此,影像质量还影响了:▶ 压缩技术▶ 存储需求▶ 传输效率随着技术进步,影像质量的标准不断提高,对于研究与开发领域,理解并提升影像质量已成为不可忽视的重要课题。在图像处理的过程中,硬件与软件除了各自扮演着不可或缺的基础角色,有效地协作能够确保图像处理过程既高效又具有优异的质量。软硬件各扮演了什么
    百佳泰测试实验室 2025-01-03 10:39 87浏览
  • Matter加持:新世代串流装置如何改变智能家居体验?随着现在智能家庭快速成长,串流装置(Streaming Device,以下简称Streaming Device)除了提供更卓越的影音体验,越来越多厂商开始推出支持Matter标准的串流产品,使其能作为智能家庭中枢,连结多种智能家电。消费者可以透过Matter的功能执行多样化功能,例如:开关灯、控制窗帘、对讲机开门,以及操作所有支持Matter的智能家电。此外,再搭配语音遥控器与语音助理,打造出一个更加智能、便捷的居家生活。支持Matter协议
    百佳泰测试实验室 2025-01-03 10:29 105浏览
  • 在测试XTS时会遇到修改产品属性、SElinux权限、等一些内容,修改源码再编译很费时。今天为大家介绍一个便捷的方法,让OpenHarmony通过挂载镜像来修改镜像内容!触觉智能Purple Pi OH鸿蒙开发板演示。搭载了瑞芯微RK3566四核处理器,树莓派卡片电脑设计,支持开源鸿蒙OpenHarmony3.2-5.0系统,适合鸿蒙开发入门学习。挂载镜像首先,将要修改内容的镜像传入虚拟机当中,并创建一个要挂载镜像的文件夹,如下图:之后通过挂载命令将system.img镜像挂载到sys
    Industio_触觉智能 2025-01-03 11:39 91浏览
  • 【工程师故事】+半年的经历依然忧伤,带着焦虑和绝望  对于一个企业来说,赚钱才是第一位的,对于一个人来说,赚钱也是第一位的。因为企业要活下去,因为个人也要活下去。企业打不了倒闭。个人还是要吃饭的。企业倒闭了,打不了从头再来。个人失业了,面对的不仅是房贷车贷和教育,还有找工作的焦虑。企业说,一个公司倒闭了,说明不了什么,这是正常的一个现象。个人说,一个中年男人失业了,面对的压力太大了,焦虑会摧毁你的一切。企业说,是个公司倒闭了,也不是什么大的问题,只不过是这些公司经营有问题吧。
    curton 2025-01-02 23:08 237浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 131浏览
  • 前言近年来,随着汽车工业的快速发展,尤其是新能源汽车与智能汽车领域的崛起,汽车安全标准和认证要求日益严格,应用范围愈加广泛。ISO 26262和ISO 21448作为两个重要的汽车安全标准,它们在“系统安全”中扮演的角色各自不同,但又有一定交集。在智能网联汽车的高级辅助驾驶系统(ADAS)应用中,理解这两个标准的区别及其相互关系,对于保障车辆的安全性至关重要。ISO 26262:汽车功能安全的基石如图2.1所示,ISO 26262对“功能安全”的定义解释为:不存在由于电子/电气系统失效引起的危害
    广电计量 2025-01-02 17:18 199浏览
  • 本文继续介绍Linux系统查看硬件配置及常用调试命令,方便开发者快速了解开发板硬件信息及进行相关调试。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。查看系统版本信息查看操作系统版本信息root@ido:/# cat /etc/*releaseDISTRIB_ID=UbuntuDISTRIB_RELEASE=20.04DISTRIB_CODENAME=focalDIS
    Industio_触觉智能 2025-01-03 11:37 102浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 130浏览
  • 车身域是指负责管理和控制汽车车身相关功能的一个功能域,在汽车域控系统中起着至关重要的作用。它涵盖了车门、车窗、车灯、雨刮器等各种与车身相关的功能模块。与汽车电子电气架构升级相一致,车身域发展亦可以划分为三个阶段,功能集成愈加丰富:第一阶段为分布式架构:对应BCM车身控制模块,包含灯光、雨刮、门窗等传统车身控制功能。第二阶段为域集中架构:对应BDC/CEM域控制器,在BCM基础上集成网关、PEPS等。第三阶段为SOA理念下的中央集中架构:VIU/ZCU区域控制器,在BDC/CEM基础上集成VCU、
    北汇信息 2025-01-03 16:01 137浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 123浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦