0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

lilihe92 来源:最后一个bug 2023-07-26 09:30 次阅读

正文

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 版权归原作者或平台所有,仅供学习参考与学术研究,如有侵权,麻烦联系删除~感谢







审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 二进制
    +关注

    关注

    2

    文章

    707

    浏览量

    41250
  • python
    +关注

    关注

    51

    文章

    4676

    浏览量

    83467

原文标题:为什么浮点运算会产生误差而整数不会?

文章出处:【微信号:最后一个bug,微信公众号:最后一个bug】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    二进制格雷码与自然二进制码的互换分析

    其中采用循环二进制编码的绝对式编码器,其输出信号是一种数字排序,不是权重码,每一位没有确定的大小,不能直接进行比较大小和算术运算,也不能直接转换成其他信号,要经过一次码变换,变成自然二进制
    的头像 发表于 09-23 16:23 6275次阅读

    二进制格雷码与自然二进制码的互换

    位、13位、14位或更高位等多种。其中采用循环二进制编码的绝对式编码器,其输出信号是一种数字排序,不是权重码,每一位没有确定的大小,不能直接进行比较大小和算术运算,也不能直接转换成其他信号,要经过一次
    发表于 03-08 14:16

    实现两个二进制除法运算

    实现两个二进制除法运算,并在八个七段数码管上进行显示实现两个二进制除法运算,并在八个七段数码管上进行显示实现两个二进制除法
    发表于 11-01 20:34

    二进制运算问题

    在LabVIEW中,如何实现一个十六进制的数取反后加1,如FFFFFFCB表示为二进制是11111111111111111111111111001011,取反后
    发表于 03-15 00:32

    二进制显示数组

    请教各位大神如何将一个十进制数组变成二进制显示??我是初学者,在数组属性里的显示格式里,二进制不可选,是不是需要数据转换???请各位大神不吝赐教,谢谢!!!!!!!
    发表于 11-28 11:21

    Labview图像二进制转换问题

    `各位大神,我想把一张图片通过变成二进制通过TCP传输,现在我把图片转换成二进制文件,但文件只有1KB大小。我想读取但是不知道二进制的图片数据类型。能否帮我画一个简易的读取
    发表于 12-29 09:26

    如何使用VHDL转换二进制中的负浮点

    关于二进制中负浮点的转换以及用VHDL编写它的问题,我确实遇到了问题。例如,num是-0.8。当我将其转换为二进制时,它就像.11001100。因为它是-ve我必须将其转换为2的补码并提供额外的MSB位。 我的问题是如何在不使用
    发表于 10-31 07:13

    如何理解二进制运算规则 二进制是如何运算

    二进制运算规则二进制运算算术运算二进制的加法:0+0=0,0+1=1 ,1+0=1, 1+1=
    发表于 12-11 17:49

    二进制数逻辑运算是怎么运算

    “与” , 其结果必为 0; 凡同 1 相“与” , 其结果不变(0 同 1 相“与”仍为 0, 1 同 1 相“与”仍为 1) 。因此, 如果一个 8 位二进制数, 想要保留其中的几位而屏蔽(清除) 掉其余
    发表于 12-25 16:36

    为什么计算机内部进行二进制运算,而不是十进制运算

    闲来没事在b站上看李永乐老师讲的这个视频,觉得挺有意思的。这篇文章主要是记一下笔记。本篇文章里面的数字若没有特殊指明,默认为二进制数字。你们有没有这样的疑问:为什么计算机内部进行二进制运算,而不是
    发表于 07-23 09:59

    浮点数转换为二进制存储的方法

    浮点数转换为二进制存储根据IEEE754标准,单精度float类型使用32比特存储,其中1位表示符号,8位表示指数,23位表示尾数;双精度double类型使用64比特存储,1位符号位,11位指数
    发表于 12-09 06:09

    把截止基极变成异通的二进制触发器电路图

    把截止基极变成异通的二进制触发器电路图
    发表于 07-03 11:35 548次阅读
    把截止基极<b class='flag-5'>变成</b>异通的<b class='flag-5'>二进制</b>触发器电路图

    二进制数的运算规则

    二进制数的运算规则  二进制数之间可以执行算术运算和逻辑运算,其规则简单,容易实现。  (1) 加法运算
    发表于 10-13 16:24 2.2w次阅读

    浮点数转换为二进制存储

    浮点数转换为二进制存储根据IEEE754标准,单精度float类型使用32比特存储,其中1位表示符号,8位表示指数,23位表示尾数;双精度double类型使用64比特存储,1位符号位,11位指数
    发表于 11-26 11:21 50次下载
    <b class='flag-5'>浮点</b>数转换为<b class='flag-5'>二进制</b>存储

    如何实现二进制和BCD码数据的相互转变

    如何实现二进制和BCD码数据的相互转变二进制码是将十进制数字表示为二进制数和十进制数的一种表
    的头像 发表于 02-18 14:51 447次阅读