可能你还不懂浮点数

在网上看到一个问题

55f66ab4be50f3d0065ff25e8608eed5.png

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

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

f274f59f61ab9725d407c535c871bef7.png

3b97e633bcc8432d1fd3ac18b58155df.png

如图:

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

b014fa2c8dd0ee6411ad041e34c9a810.png

前言

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

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

58a9d69db2a38d1db4295428d2a81216.png

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

26ffb81d94ca6ca3c854f24372e00629.png

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

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

ae94104f6384a740329488b101daa241.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

c55dd38c49f1134962b5e0ec49b2971a.png

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

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

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

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

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

f0f6baa2986ce2eff3114a747b175ca0.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,所以精准度自然提高许多

107d20ba8ebccb4c11a021c8142dd30a.png

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

600f7ba7e62a6672ca01c787e5d5236d.png

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

3907a5a94f52fc994f73e8c7b0389f95.png

解决方法

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

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

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

6b2c25b558a9e6f19773795bcb7e0395.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 绝对不会出错、也不会有任何误差(其他语言也有类似的模组)

c7c9675703f9f86cf0a441fcd0b2f70b.png

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

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

总结

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

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

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

后记

这是我第一次写这种几乎是纯原理的文章,不管你是觉得很有趣,还是觉得太理论了不知道学这要干嘛,都欢迎你在下方留言跟我说,或是透过拍手表达你的意见,这样我也比较能知道你们喜欢什么类型的文章,谢谢大家~

参考资料

  • https://zh.wikipedia.org/wiki/IEEE_754

  • https://www.h-schmidt.net/FloatConverter/IEEE754.html

  • https://zh.wikipedia.org/wiki/%E6%B5%AE%E7%82%B9%E6%95%B0

文章转自:https://medium.com/starbugs/see-why-floating-point-error-can-not-be-avoided-from-ieee-754-809720b32175

ac932bf0bbb975857d2eba7b0b9d526a.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RTP协议的封装

最近一段时间学习了RTP协议相关的内容&#xff0c;一方面为了将自己学到的部分记录下来&#xff0c;便于后续查找&#xff0c;另一方面用于记录笔记 一个协议的封装是为了满足协议的功能需求的。从前面提出的功能需求&#xff0c;可以推测出RTP封装中应该有同步源和时戳等字段&…

Alpha冲刺(7/10)

团队信息 队名&#xff1a;爸爸饿了组长博客&#xff1a;here作业博客&#xff1a;here组员情况 组员1&#xff08;组长&#xff09;&#xff1a;王彬 过去两天完成了哪些任务 学会了POSTMAN的使用&#xff0c;对后端已经完成的接口进行了收发消息正确性的验证推进项目进度&…

epoll模型之服务器设计

Linux2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用&#xff0c;即比较多的用到select函数。1、为什么select落后首先&#xff0c;在Linux内核中&#xff0c;select所用到的FD_SET是有限的&#xff0c;即内核中有个参数__FD_SETSIZE…

不限学历、不限学校、华为天才少年招聘

我在前同事的朋友圈看到的招聘信息。不限学历&#xff0c;不限学校我相信这个规则一定会让后续的很多企业效仿&#xff0c;工作至今&#xff0c;遇到很多能力很强但是学历一般的人&#xff0c;而对于面试者&#xff0c;可以大胆的说出那句话&#xff0c;人家华为都不限制学校学…

linux学习第九天 (Linux就该这么学)

今天讲了raid0 至少两块盘串联在一起&#xff0c;读写性能提升&#xff0c;但不具备数据备份和错误修复能力&#xff0c;RAID1把两块盘绑定&#xff0c;在写入数据时&#xff0c;同时写入到多块硬盘设备&#xff0c;raid5推荐使用&#xff0c;10推荐使用 LVM,今天是在外面加班…

[Windows Phone] 为应用添加后台计划任务 – Scheduled Task Agent

前段时间做过一个天气应用&#xff0c;一直是只支持前台获取数据&#xff0c;上周末参加了Windows Phone的CodeJam和高手们交流了一下&#xff0c;发现实现后台定时更新功能也不是很难&#xff0c;于是在网上找一些资料&#xff0c;在找资料的过程中发现&#xff0c;网上的一些…

全能终端神器MobaXterm

摘要&#xff1a;现今软件市场上有很多终端工具&#xff0c;比如&#xff1a;secureCRT、Putty等等。secureCRT其实也是一款很强大的终端工具&#xff0c;但它是收费软件&#xff0c;一般公司不允许使用。Putty&#xff0c;非常小巧&#xff0c;免费软件&#xff0c;但是不支持…

入群问的这道C题目,还没人答对

最近好几个同学加我微信&#xff0c;让我帮忙拉他入群&#xff0c;然后我就随手问了个题目&#xff0c;如下上面回答的答案都是不正确的sizeof是 C语言的关键字&#xff0c;这个关键字是用来计算传入参数占用的内存字节数。比如#include <stdio.h> int main() {int a 1;…

常见单元测试工具介绍

1. C/C语言开发的首选利器- CTest 以前在windows平台下的开发&#xff0c;使用的框架主要是MFC&#xff0c;以及console工程&#xff08;基于win32SDK&#xff09;&#xff0c;属于纯C/C开发的范畴。 因此&#xff0c;使用的单元测试工具&#xff0c;主要有CTest和CppUni…

win10系统的服务器在哪里设置密码,window10怎么设置密码

window10怎么设置密码Windows 10 系统中可以用自己喜欢的图片结合手势来设置登陆密码&#xff0c;很酷很有个性&#xff0c;如何操作呢&#xff1f;1、从开始菜单中打开“设置”应用(也可以通过其他的方式打开“设置”应用&#xff1b;从通知中心打开&#xff1b;从搜索框中搜索…

Linux 启动优化实战-2.41 秒启动应用!

系统启动是一个大问题&#xff0c;前段时间有同学也问了我这个问题&#xff0c;不仅仅是Linux&#xff0c;Android 下面的启动优化也可以借助bootchar来分析。下面正文是老吴的实操过程。哦&#xff0c;对了&#xff0c;上篇文章有同学问文章的封面&#xff0c;这里贴出来&…

腾达fh307没有显示服务器名,腾达(Tenda)FH307路由器上网设置 | 192路由网

本文介绍了腾达(Tenda)FH307路由器的安装、上网设置方法&#xff0c;同时分别介绍了“ADSL(PPPOE)拨号”、“动态IP”、“静态IP”三种上网方式的区别&#xff0c;以及在腾达(Tenda)FH307路由器上的详细设置。腾达(Tenda)FH307无线路由器一台新购买的腾达FH307路由器实现上网&a…

记得重用layout

2019独角兽企业重金招聘Python工程师标准>>> 这样的布局&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android" android:layout_width"…

sql2000-2 4/19

SQL2000客戶的上次問題後來H盤自動毀掉了昨天換了顆新硬碟,當資料庫挂上之後,H一直又提示錯誤,後來還是將資料庫路徑設為I傍晚時分親眼目睹了H所有資料被自動刪除的過程,待服務器重新啟動後H無法存取萬幸的是早一步將資料庫路徑作了更動在還原資料庫的過程中用克隆的BAK還原一直…

五一加班

前几年五一放假&#xff0c;我们会去小云家&#xff0c;五一也是荔枝成熟的时候&#xff0c;有一年吃上刚从树上摘的荔枝&#xff0c;现在还能记起那种味道&#xff0c;也是那次之后&#xff0c;想奢入简很难了&#xff0c;荔枝也一定要吃新鲜的了。今年一直还没看到荔枝的影子…

win10系统能做域服务器吗,Win10 LTSC 加入 Windows Server 2019 域服务器

前面的文章已经创建了域服务器、创建了域组织单位、用户组、用户&#xff0c;下面是如何把一台 Win10 LTSC 系统的电脑加入到Windows Server 2019 域服务器&#xff1b;0x01 加域准备修改计算机名、修改IP地址DNS指向 Windows Server 2019 域控制器&#xff1b;注意&#xff1a…

离职就打低绩效,这样对吗?

应该不止听见一个人说过&#xff0c;担心离职遇到各种不爽的事情&#xff0c;比如卡你的离职时间&#xff0c;比如让你背很低的绩效&#xff0c;比如你今年的年终奖就没有了&#xff0c;再比如&#xff0c;你和原来玩得好的同事突然就没话说了。我记得很清楚的事情是&#xff0…

+ 网页制作效果常用代码

控制横向和纵向滚动条的显隐&#xff1f;<body style"overflow-y:hidden"> 去掉x轴<body style"overflow-x:hidden"> 去掉y轴<body scroll"no">不显表格变色<TD οnmοuseοver"this.style.backgroundColor#FFFFFF&qu…

PID算法原理介绍

先来彻底搞懂PID到底是啥&#xff1f;PID&#xff0c;就是“比例&#xff08;proportional&#xff09;、积分&#xff08;integral&#xff09;、微分&#xff08;differential&#xff09;”&#xff0c;是一种很常见的控制算法。在工程实际中&#xff0c;应用最为广泛的调节…

打败opencv ,哦,是快了3倍

大家好&#xff0c;本文转自我一个读者朋友Homio的文章&#xff0c;推荐给大家&#xff0c;希望对做这方便的同学有所帮助。程序员&#xff0c;哦&#xff01;不&#xff01;软件工程师们都对opencv很熟悉&#xff0c;它在工作学习研究中起到了不可或缺的作用。但是它臃肿的身躯…