【小思考】Python的float转换精度损失所想到的
首先,为啥会要讨论这个问题。
我得为昨天拖了小组后腿深表歉意。其实程序逻辑很快就理通了的,但自己总是会因为各种各样的小问题束缚手脚,看接下来这个图片:
稍微有数据敏感性的同学就能看出,中间这么一大堆又是0000又是999还是这么多位的小数,一看就是异常数据。这块数据的产生,源于代码里对两个字符串做了float转换并相减,导致出现了这种数据异常的错误。那么问题来了,1.这种异常是如何产生的?2.有哪些方法可以解决这种问题呢?3.编程中间还有哪些与这个问题相关的注意事项呢?
第一部分:这种异常是如何产生的呢?
我们先来看演示:
看来,直接输出float型数据,以及对字符串进行的float转换,本身并没有什么问题,那么为什么浮点数相减就会出现这个可恶的小尾巴呢?我们有必要从计算机本身数字加减的机制进行探究。有学习过《计算机组成原理》等基本课程、哪怕只是简单了解计算机内部运行机制的同学都明白,计算机内部的加减乘除都是要把数字转化成为二进制实现的。那么,我们此处的浮点数,也要转换为二进制,才能进行计算。Python内浮点数是用机器上浮点数的本机双精度(64 bit)表示的。提供大约17位的精度和范围从-308到308的指数。和C语言里面的double类型相同【可参考C语言double类型的解释】。
我们来看一个简单的例子。十进制1.1转换成二进制是什么数?十进制整数部分转化成二进制,用短除法处以2倒序取余。小数部分转化为二进制是用乘法乘2正序取整。见下面一个浮点数转二进制数的例子。
1.10整数部分就是1,转换成二进制1(这里整数转二进制不再赘述)
小数部分:0.1
0.1*2=0.2取整数部分0,基数=0.2
0.2*2=0.4取整数部分0,基数=0.4
0.4*2=0.8取整数部分0,基数=0.8
0.8*2=1.6取整数部分1,基数=1.6-1=0.6
0.6*2=1.2取整数部分1,基数=1.2-1=0.2
0.2*2=0.4取整数部分0,基数=0.4
.
.
.
直至基数为0。1.1用二进制表示为:1.000110...xxxx....(后面表示省略)
关于之前的演示,相当于,因为3.4的存储,发生了精度损失(3.5不会,因为3.5的二进制是11.1,补码存储依然不会发生精度损失),所以在相减的时候,发生了一次精度损失,最后结果存储的时候,再次发生一次精度损失。所以,才会出现最后的小尾巴情况。
第二部分:有哪些方法可以解决这个问题呢?
解决这个问题?不存在的,除非是提高精度——让计算机内能够完整的存储数字的二进制(二进制补码)表示,否则的话,只要有精度损失,就指不定什么时候会冒出来小尾巴。我们追求的解决,自然也是从提高精度,和“表面看起来正确”这两条道路去追求。
提高精度——Python本身自带的float已经是可支持浮点数的最高精度形式。当然,这个肯定是不能阻挡我们对更高精度的要求,这里可以自己实现高精度的数据形式,也可以使用Python扩展模块:Decimal。使用Decimal本身需要导入decimal包,初始化decimal数据可以使用整型数据和字符串,而不能使用float型数据,正如之前我们所说的那样,某些浮点数存储会发生精度损失——这意味着float本身就不够精确。
当然,还有很多抖机灵的方法,比如说结果转换成字符串然后再截取?!
你可能体会不到,这个是一种针对数据波动范围相对确定,相当实用的方法——虽然应该没有任何一个脑子正常的程序员会推崇这种方法。这种方法就是追求的“表面上看起来正确”,你看,最后的显示出来的结果不就是-0.1么?
自然,还有print的%精度控制,这里就不赘述。而且也不想详述这个,毕竟这个惊为天人的字符串截取方法,都还是对字符串进行了处理,而%精度控制只是显示的时候做了处理,可真是够“表面”的。
不得不说,也是受这种方法的启发,本人使用的方法,是利用Python int转换“舍去小数点后所有数字”的特点,把原浮点数乘以需要保留精度的位数,然后转换成整数,再除回去,这样就形成了“表面正确”的数据,效果不要太好。
总结一下!解决这个精度损失带来的“恶魔小尾巴”问题,我们大体上有提高数据格式精度和只追求最终显示改变两大思路。
提高数据格式精度:使用扩展包decimal
只追求最终显示改变:printf %精度控制 ,字符串截取指定位数,先移动小数点、转换成整型舍去末尾、再把小数点移动回来。等方法。
第三部分:编程中间还有哪些和这个问题相关的注意事项呢?
这个小尾巴让我可谓一开始是焦头烂额,也严重耽误了小组研究进度。通过我们之前的探究,可以发现,浮点数本身表示由于受计算机限制,经常是不精确的。所以,日常数据中,最好不要用浮点数。
可能有些人觉得精度损失一些没有什么,然而浮点数的精度损失关键时候可不只只是精度损失,甚至会影响流程控制!浮点数不只有Python里面有,咱们用更加基本的C++来说明这个问题:
当精度损失已经让程序的走向开始不符合逻辑的时候,你还会轻视这个问题么?
这里给广大同仁们分享一篇专门讲解浮点数的文章,深入了解,真的有很多可圈可点之处!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
以太坊DApp如何用IPFS存储并调用数据
正在构建的一个Dapp。Dapp包括一些用户数据,如电子邮件、姓名和个人图片等。我想将用户数据内容存储在IPFS中,通过一个JSON对象,并用IPFS hash处理过。我怎样才能把这个数据和一个特定的用户联系起来呢?也就是说在与DAPP的交互时将用户与IPF中存储的数据关联起来。要使用用户密码和某种类型的密码来完成的吗? 例如,用户A对使用dapp感兴趣,然后提供了他或她的电子邮件、姓名和一些个人图片。在与DApp的任何后续交互(如评论或帖子)都会将该用户和IPFS中相应的用户数据相关联。不知道该怎么做。 dapp和ipfs数据关联说明 使用像以太坊这种区块链平台的一个优势是可以构建一个零点击登录。如果我们用web3.eth.accounts[0]确认下就可以证明用户控制该帐户的地址的私钥是否有效。 如果想就像数据库一样使用IPFS,建议的方法是: 请注意,在大多数去中心化的系统中,客户端会发生很多动作。 用户注册 用户有以太坊账户。 注册用户数据被收集到JSON对象中 创建一个文件,将JSON对象写入文件 传递文件到IPFS 获取文件哈希值(基本上是它的IPFS的地址) 将IPFS的...
- 下一篇
匿名内部类方式构建对象导致序列化失败
问题描述: 以下代码为问题代码: public class ItemDO implements Serializable { private static final long serialVersionUID=-463144769925355007L; ... private Map<String,String> langAndTitleMap; ... } public class ItemMultiLangDecorator implements ItemDecorator { ... @Override public ItemDO getItemDO() throws IcException { .
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS8编译安装MySQL8.0.19
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装Nodejs环境
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS关闭SELinux安全模块
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16