全球最实用的IT互联网信息网站!

AI人工智能P2P分享&下载搜索网页发布信息网站地图

当前位置:诺佳网 > 电子/半导体 > 嵌入式技术 >

浮点运算的尾数部分是如何转变成二进制的?

时间:2023-07-26 09:30

人气:

作者:admin

标签: 二进制  python 

导读:想一下,为什么第一个打印的和预设值不同,但是第二个是相同的?...

正文

aff4650a-2aef-11ee-a368-dac502259ad0.png

然后看到这篇关于浮点数的文章,希望大家看了之后有所启发。

想一下,为什么第一个打印的和预设值不同,但是第二个是相同的?

b02a20d2-2aef-11ee-a368-dac502259ad0.png

b05b70ba-2aef-11ee-a368-dac502259ad0.png

如图:

尾数部分是如何转变成二进制的?

b07a8b4e-2aef-11ee-a368-dac502259ad0.jpg

前言

很多人在初学写程式时都会遇到所谓的浮点误差,如果你到目前都还没被浮点误差雷过,那只能说你真的很幸运XD。

以下图Python 的例子来说0.1 + 0.2并不等于0.3,8.7 / 10也不等于0.87,而是0.869999…,真的超怪der

b08a106e-2aef-11ee-a368-dac502259ad0.png

但这绝对不是什么神bug,也不是Python 设计得不好,而是浮点数在做运算时必然的结果,所以即便是到了Node.js 或其他语言也都是一样。

b0a9d37c-2aef-11ee-a368-dac502259ad0.png

电脑如何储存一个整数(Integer)

在讲为什么会有浮点误差之前,先来谈谈电脑是怎么用0 跟1 来表示一个整数,大家应该都知道二进制这个东西:像101代表2² + 2⁰ 也就是5、1010代表2³ + 2¹ 也就是10。

b0e3d7b6-2aef-11ee-a368-dac502259ad0.png

如果是一个unsigned 的32 bit 整数,代表他有32 个位置可以放0 或1,所以最小值就是0000...0000也就是0,而最大值1111...1111代表2³¹ + 2³⁰ + … + 2¹ + 2⁰ 也就是4294967295。

从排列组合的角度来想,因为每一个bit 都可以是0 或1,整个变数值有2³² 种可能性,所以可以精确的表达出0 到2³²-1 中任一个值,不会有任何误差。

浮点数(Floating Point)

虽然从0 到2³²-1 之间有很多很多个整数,但数量终究是有限的,就是2³² 个那么多而已;但浮点数就大大的不同了,大家可以这样想:在1 到10 这个区间中只有十个整数,但却有无限多个浮点数,譬如说5.1、5.11、5.111 等等,再怎么数都数不完。

但因为在32 bit 的空间中就只有2³² 种可能性,为了把所有浮点数都塞在这个32 bit 的空间里面,许多CPU 厂商发明了各种浮点数的表示方式,但若各家CPU 的格式都不一样也很麻烦,所以最后是以IEEE发布的IEEE 754作为通用的浮点数运算标准,后来的CPU 也都遵循这个标准进行设计。

IEEE 754

IEEE 754 里面定义了很多东西,其中包括单精度(32 bit)、双精度(64 bit)跟特殊值(无穷大、NaN)的表示方式等。

正规化

以8.5 这个符点数来说,如果要变成IEEE 754 格式的话必须先做正规化:把8.5 拆成8 + 0.5 也就是2³ + 1/2¹,接着写成二进位变成1000.1,最后再写成1.0001 x 2³,跟十进位的科学记号满像的。

单精度浮点数

在IEEE 754 中32 bit 浮点数被拆成三个部分,分别是sign、exponent 跟fraction,加起来总共是32 个bit。

b0f28c34-2aef-11ee-a368-dac502259ad0.png

sign:最左侧的1 bit 代表正负号,正数的话sign 就为0,反之则是 1。

exponent:中间的8 bit 代表正规化后的次方数,采用的是超127格式,也就是3 还要加上127 = 130。

fraction:最右侧的23 bit 放的是小数部分,以1.0001 来说就是去掉1. 之后的000。

所以如果把8.5 表示成32 bit 格式的话就会是这样:

这图我画超久的,请大家仔细看XD。

b10ada5a-2aef-11ee-a368-dac502259ad0.png

什么情况下会不准呢?

刚刚8.5 的例子可以完全表示为2³+ 1/2¹,是因为8 跟0.5 刚好都是2 的次方数,所以完全不需要牺牲任何精准度。

但如果是8.9 的话因为没办法换成2 的次方数相加,所以最后会被迫表示成1.0001110011… x 2³,而且还会产生大概0.0000003 的误差,好奇结果的话可以到IEEE-754 Floating Point Converter网站上玩玩看。

双精度浮点数

上面讲的单精度浮点数只用了32 bit 来表示,为了让误差更小,IEEE 754 也定义了如何用64 bit 来表示浮点数,跟32 bit 比起来fraction 部分大了超过两倍,从23 bit 变成52 bit,所以精准度自然提高许多。

b144fd20-2aef-11ee-a368-dac502259ad0.png

以刚刚不太准的8.9 为例,用64 bit 表示的话虽然可以变得更准,但因为8.9 无法完全写成2 的次方数相加,到了小数下16 位还是出现误差,不过跟原本的误差0.0000003 比起来已经小了很多。

b177a482-2aef-11ee-a368-dac502259ad0.png

类似的情况还有像Python 中的1.0跟0.999...999是相等的、123跟122.999...999也是相等的,因为他们之间的差距已经小到无法放在fraction 里面,所以就二进制的格式看来他们每一个bit 都一样。

b1a05e40-2aef-11ee-a368-dac502259ad0.png

解决方法

既然无法避免浮点误差,那就只好跟他共处了(打不过就加入?),这边提供两个比较常见的处理方法。

设定最大允许误差ε (epsilon)

在某些语言里面会提供所谓的epsilon,用来让你判断是不是在浮点误差的允许范围内,以Python 来说epsilon 的值大概是2.2e-16。

b1c04c00-2aef-11ee-a368-dac502259ad0.png

所以你可以把0.1 + 0.2 == 0.3改写成0.1 + 0.2 — 0.3 <= epsilon,这样就能避免浮点误差在运算过程中作怪,也就可以正确比较出0.1 加0.2 是不是等于0.3。

当然如果系统没提供的话你也可以自己定义一个epsilon,设定在2 的-15 次方左右。

完全使用十进位进行计算

之所以会有浮点误差,是因为十进制转二进制的过程中没办法把所有的小数部分都塞进fraction,既然转换可能会有误差,那干脆就不要转了,直接用十进制来做计算!!

在Python 里面有一个module 叫做decimal,它可以帮你用十进位来进行计算,就像你自己用纸笔计算0.1 + 0.2 绝对不会出错、也不会有任何误差(其他语言也有类似的模组)。

b1fce94e-2aef-11ee-a368-dac502259ad0.png

自从我用了Decimal 之后不只bug 不见了,连考试也都考一百分了呢!

虽然用十进位进行计算可以完全躲掉浮点误差,但因为Decimal 的十进位计算是模拟出来的,在最底层的CPU 电路中还是用二进位在进行计算,所以跑起来会比原生的浮点运算慢非常多,所以也不建议全部的浮点运算都用Decimal 来做。

总结

回归到这篇文章的主题:「为什么浮点误差是无法避免的?」,相信大家都已经知道了。

至于你说知道IEEE 754 的浮点数格式有什么用吗?好像也没什么特别的用处XD,只是觉得能从浮点数的格式来探究误差的成因很有趣而已,感觉离真相又近了一点点。

而且说不定哪天会有人问我「为什么浮点运算会产生误差而整数不会」,那时我就可以有自信的讲解给他听,而不是跟他说「反正浮点运算就是会有误差,背起来就对了」

来源:https://medium.com/starbugs/see-why-floating-point-error-can-not-be-avoided-from-ieee-754-809720b32175 版权归原作者或平台所有,仅供学习参考与学术研究,如有侵权,麻烦联系删除~感谢







审核编辑:刘清

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信