Python的浮点数损失精度问题(转)
a+=0.1
(下面是精度漏洞的问题-转自其它)
从下面这段脚本体现出来:
>>> x = 0.0
>>> for i in range(10):
x += 0.1
print(x) 0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
>>>
即:为什么有几行的输出看起来不对?
因为 Python 中使用双精度浮点数来存储小数。在 Python 使用的 IEEE 754 标准(52M/11E/1S)中,8字节64位存储空间分配了52位来存储浮点数的有效数字,11位存储指数,1位存储正负号,即这是一种二进制版的科学计数法格式。虽然52位有效数字看起来很多,但麻烦之处在于,二进制小数在表示有理数时极易遇到无限循环的问题。其中很多在十进制小数中是有限的,比如十进制的 1/10,在十进制中可以简单写为 0.1 ,但在二进制中,他得写成:0.0001100110011001100110011001100110011001100110011001…..(后面全是 1001 循环)。因为浮点数只有52位有效数字,从第53位开始,就舍入了。这样就造成了标题里提到的”浮点数精度损失“问题。 舍入(round)的规则为“0 舍 1 入”,所以有时候会稍大一点有时候会稍小一点。
Python 的浮点数类型有一个 .hex()方法,调用能够返回该浮点数的二进制浮点数格式的十六进制版本。这话听着有点绕,其实是这样的:本来浮点数应该有一个 .bin() 方法,用来返回其二进制浮点数格式。如果该方法存在的话,它看起来就像这样(p-4表示×2-4,或者可以简单理解为小数点 左移 4 位):
>>> (0.1).bin()#本方法其实并不存在
'1.1001100110011001100110011001100110011001100110011010p-4'
但是这个字符串太长了,同时因为每 4 位二进制字符都可以换算成 1 位十六进制字符,于是Python就放弃了给浮点数提供 .bin() 方法,改为提供 .hex() 方法。这个方法将上面输出字符串的 52 位有效数字转换成了 13 位十六进制数字,所以该方法用起来其实是这样的(注:二进制浮点数中小数点前的“1”不包含于那 52 位有效数字之中):
>>> (0.1).hex()
'0x1.999999999999ap-4'
前面的 0x 代表十六进制。p-4 没变,所以需要注意,这里的 p-4 还是二进制版的,也就是说在展开本格式的时候,你不能把小数点往左移 4 位,那样就相当于二进制左移 16 位了。前面提到过,小数点前这个“1”是不包含于 52 位有效数字之中的,但它确实是一个有效的数字呀,这是因为,在二进制浮点数中,第一位肯定是“1”,(是“0”的话就去掉这位,并在指数上-1)所以就不保存了,这里返回的这个“1”,是为了让人看懂而加上的,在内存的 8 位空间中并没有它。所以 .hex() 方法在做进制转换的时候,就没有顾虑到这个“1”,直接把 52 位二进制有效数字转换掉就按着原来的格式返回了。因此这个 .hex() 方法即使名义上返回的是一个十六进制数,它小数点前的那一位也永远是“1”,看下面示例:
>>> float.fromhex('0x1.8p+1') == float.fromhex('0x3.0p+0')
True
一般我们用十六进制科学计数法来表示 3.0 这个数时,都会这么写“0×3.0p+0”。但是 Python 会这么写“0×1.8p+1”,即“1.1000”小数点右移一位变成“11.000”——确实还是 3.0 。就是因为这个 1 是直接遗传自二进制格式的。而我一开始没有理解这个 .hex() 的意义,还画蛇添足地自定义了一个 hex2bin() 方法,后来看看真是没必要啊~
而为了回应人们在某些状况下对这个精度问题难以忍受的心情(雾),Python 提供了另一种数字类型——Decimal 。他并不是内建的,因此使用它的时候需要 import decimal 模块,并使用 decimal.Decimal() 来存储精确的数字。这里需要注意的是:使用非整数参数时要记得传入一个字符串而不是浮点数,否则在作为参数的时候,这个值可能就已经是不精确的了:
>>> Decimal(0.1) == Decimal('0.1')
False
在进一步研究到底损失了多少精度,或者说,八字节浮点数最多可以达到多少精度的问题之前,先来整理一下小数和精度的概念。本篇中讨论的小数问题仅限于有理数范围,其实有理数也是日常编程中最常用到的数。有理数(rational number)一词派生于“比(ratio)”,因此并不是指“有道理”的意思。有理数的内容扩展自自然数,由自然数通过有理运算(+ – * /)来得到的数系称为有理数,因此可以看到它较自然数扩充了:零、负整数和分数的部分。有理数总可以写成 p/q 的形式,其中 p、q 是整数且 q ≠ 0,而且当 p 和 q 没有大于 1 的公因子且 q 是正数的时候,这种表示法就是唯一的。这也就是有理数被称为 rational number 的原因,说白了就是分数。实际上 Python 的 float 类型还有一个 .as_integer_ratio() 的方法,就可以返回这个浮点数的最简分数表示,以一个元组的形式:
>>> (0.5).as_integer_ratio()
(1, 2)
然后为了更直观地表现,人们又开始用无限小数的形式表示有理数(分数)。而其中从某一位开始后面全是 0 的特殊情况,被称为有限小数(没错,无限小数才是本体)。但因为很多时候我们并不需要无限长的小数位,我们会将有理数保存到某一位小数便截止了。后面多余小数的舍入方式便是“四舍五入”,这种方式较直接截断(round_floor)的误差更小。在二进制中,它表现为“0 舍 1 入”。当我们舍入到某一位以后,我们就可以说该数精确到了那一位。如果仔细体会每一位数字的含义就会发现,在以求得有限小数位下尽可能精确的值为目的情况下,直接截断的舍入方式其实毫无意义,得到的那最后一位小数也并不精确。例如,将 0.06 舍入成 0.1 是精确到小数点后一位,而把它舍入成 0.0 就不算。因此,不论是在双精度浮点数保留 52 位有效数字的时候,还是从双精度浮点数转换回十进制小数并保留若干位有效数字的时候,对于最后一位有效数字,都是需要舍入的。
下图是一个(0,1)之间的数轴,上面用二进制分割,下面用十进制分割。比如二进制的 0.1011 这个数,从小数点后一位一位的来看每个数字的意义:开头的 1 代表真值位于 0.1 的右侧,接下来的 0 代表真值位于 0.11 的左侧,再接下来的 1 代表真值位于 0.101 的右侧,最后的 1 代表真值位于 0.1011 的右侧(包含正好落在 0.1011 上这种情况)。使用 4 位二进制小数表示的 16 个不同的值,除去 0,剩下的 15 个数字正好可以平均分布在(0,1)这个区间上,而十进制只能平均分布 9 个数字。显然 4 位二进制小数较于 1 位十进制小数将此区间划分的更细,即精度更高。
把 0.1 的双精度版本(0×1.999999999999ap-4)展开成十进制。这里使用了 Decimal 类型,在给他赋值的时候,他会完整存储参数,但是要注意的是,使用 Decimal 进行运算是会舍入的,保留的位数由上下文决定。使用 decimal 模块的 getcontext() 方法可以得到上下文对象,其中的 prec 属性就是精度。下面还使用了 print() 方法,这是为了好看:
>>> print(Decimal(0.1))
0.1000000000000000055511151231257827021181583404541015625
得到的这个十进制浮点数有效数字足有 55 位。虽然从二进制到十进制这个过程是完全精确的,但因为在存储这个二进制浮点数的时候进行了舍入,所以这个 55 位的十进制数,较于最初的 0.1 并不精确。至于到底能精确到原十进制数的哪一位,可以这么算: 2**53 = 9007199254740992 ≈ 10**16 ,(这里 53 算上了开头的“1”),即转换后的十进制小数的第 16 位有效数字很可能是精确的(第 15 位肯定是精确的)。换句话说,如果要唯一表示一个 53 位二进制数,我们需要一个 17 位的十进制数(但即使这样,我们也不能说对应的十进制和二进制数完全相等,他们只不过在互相转换的时候在特定精度下可以得到相同的的值罢了。就像上面例子中显示的,精确表示”0.1“的双精度版本,需要一个 55 位的十进制小数)。
不过可以看到,如果要保证转换回来的十进制小数与原值相等,那么只能保证到 15 位,第 16 位只是“很可能是精确的”。而且第 15 位的精确度也要依赖于第 16 位的舍入。实际上在 C++ 中,我看到有别人讲,double 类型的十进制小数就是保留 15 位的(这点我自己并不清楚)。所以如果 Python 的 float 类型的 __str__() 和 __repr__() 方法选择返回一个 15 位的小数,那么就不会出现本文讨论的第一个问题了。不论是早期的“0.10000000000000001”还是本文中出现的“0.30000000000000004”或者“0.7999999999999999”,我们可以看到它的不精确都是因为保存了过多位的有效数字,16 或 17 。
从下面的脚本中可以看得更加清楚:
>>> a=0.0
>>> for i in range(10):
a += 0.1
print(a)
print('%.17f'%a)
print('-'*19) 0.1
0.10000000000000001
-------------------
0.2
0.20000000000000001
-------------------
0.30000000000000004
0.30000000000000004
-------------------
0.4
0.40000000000000002
-------------------
0.5
0.50000000000000000
-------------------
0.6
0.59999999999999998
-------------------
0.7
0.69999999999999996
-------------------
0.7999999999999999
0.79999999999999993
-------------------
0.8999999999999999
0.89999999999999991
-------------------
0.9999999999999999
0.99999999999999989
-------------------
上面短横线对齐的是第 17 位。虽然在这里第 16 位全部是精确的,但如果为了保证 100% 的准确率的话,还是需要舍入到第 15 位。另外一个细节,上面的例子其实有一个问题,就是使用 0.1++ 这种方式的时候,实际累加的是一个不精确的数字,所以有可能造成误差的放大。不过这里依然没有改正,是因为 0.5 那行,突然恢复真值了。这也不是因为后面藏了其他数字没有显示出来,我们来看一下:
>>> '%.60f'%(0.1+0.1+0.1+0.1+0.1)
'0.500000000000000000000000000000000000000000000000000000000000'
>>> print(Decimal(0.1+0.1+0.1+0.1+0.1))
0.5
这里使用了一个格式限定符的示例。它的作用类似于 print Decimal。区别仅在于 Decimal 自己知道应该显示多少位,而格式化限定符不知道。(一般双精度浮点数转换过来不超过 100 位)。因为不打算继续深究了,所以就当这个“0.5”是个意外吧~如果想避免误差叠加,可以写成“i/10”的格式。
所以对于两种,不像十六进制和二进制般正好是指数关系的进制,永远都无法在各自的某一位上具有相同的精度。即 2m = 10n 这个等式没有使 m,n 同时为整数的解。但至少还可以构建一个精度包含的关系,比如上面 24 > 101 ,那么我们就说“4 位二进制精度高于 1 位十进制精度”从而通过 4 位二进制数转储 1 位十进制数的时候,总是精确的,反之则不然。同理根据这个不等式:1015 < 253 <1016 ,双精度浮点数的精度最高也就蕴含(不是等价)到十进制的 15 位了。另外虽然这种转化看起来浪费了很大的精度(第 16 位在很大概率上也是精确的),有趣的是,210 = 1024,却和 103 = 1000 离的很近。因此一般我们可以通过这个关系来近似推导精度关系。
Python之☞float浮点数精度问题的更多相关文章
-
【小思考】Python的float转换精度损失所想到的
首先,为啥会要讨论这个问题. 我得为昨天拖了小组后腿深表歉意.其实程序逻辑很快就理通了的,但自己总是会因为各种各样的小问题束缚手脚,看接下来这个图片: 稍微有数据敏感性的同学就能看出,中间这么一大堆又 ...
-
Python中浮点数精度处理
Python中,浮点数运算,经常会碰到如下情况: 出现上面的情况,主要还是因浮点数在计算机中实际是以二进制保存的,有些数不精确.比如说: 0.1是十进制,转化为二进制后它是个无限循环的数:0.0001 ...
-
用decimal模块增加python的浮点数精度
浮点数python默认是17位精度,也就是小数点后16位(16位以后的全部四舍五入了),虽然有16位,但是这个精度越往后越不准. 如果有特殊需求,需要更多的精度,可以用decimal模块,通过更改其里 ...
-
Python numpy 浮点数精度问题
Python numpy 浮点数精度问题 在复现FP(fictitious play, Iterative solution of games by fictitious play-page393)算 ...
-
关于python浮点数精度问题计算误差的原因分析
在python中使用浮点数运算可能会出现如下问题 a = 0.1+0.2print(a) 输出的结果是 0.30000000000000004 原因如下: 出现上面的情况,主要还是因浮点数在计算机中实 ...
-
mingw fbx sdk /浮点数精度
接下来要做一个linux下的程序了. 下载linux version fbx sdk tar zxvf ...gz 按照安装说明 提升权限并没什么用 还是,cannot execute bin ...
-
Java 浮点数精度丢失
Java 浮点数精度丢失 问题引入 昨天帮室友写一个模拟发红包抢红包的程序时,对金额统一使用的 double 来建模,结果发现在实际运行时程序的结果在数值上总是有细微的误差,程序运行的截图: 输入依次 ...
-
WebGL着色器32位浮点数精度损失问题
问题 WebGL浮点数精度最大的问题是就是因为js是64位精度的,js往着色器里面穿的时候只能是32位浮点数,有效数是8位,精度丢失比较严重. 这篇文章里讲了一些处理方式,但是视坐标这种方式放在我们的 ...
-
java浮点数精度问题解决方法
基础知识回顾: BigDecimal.setScale()方法用于格式化小数点setScale(1)表示保留一位小数,默认用四舍五入方式 setScale(1,BigDecimal.ROUND_DOW ...
随机推荐
-
SQL Server日期时间格式转换字符串详解 (详询请加qq:2085920154)
在SQL Server数据库中,SQL Server日期时间格式转换字符串可以改变SQL Server日期和时间的格式,是每个SQL数据库用户都应该掌握的.本文我们主要就介绍一下SQL Server日 ...
-
as3 Point
首先我们看看Point类的属性:x:Number——该点的水平坐标y:Number——该点的垂直坐标length:Number——从(0,0)到此点的线段长度(只读属性)从length属性我们可以看到 ...
-
[设计模式] javascript 之 适配器模式
适配器模式说明 说明: 适配器模式,一般是为要使用的接口,不符本应用或本系统使用,而需引入的中间适配层类或对象的情况: 场景: 就好比我们买了台手机,买回来后发现,充电线插头是三插头,但家里,只有两插 ...
-
Java学习-031-JSON 之五 -- 特定数据获取(JSONObject满足特定键值)
前面几篇博文分别讲述了 JSON 的 概要知识.简单数据获取.封装cssSelector数据获取方法.JSONObject 是否包含 key_value,请自行阅读相关博文. 在日常的接口测试脚本编写 ...
-
Sql清理日志文件
场景: 我们导入MR数据时发现磁盘空间不够用了,导致的结果就是我们的程序很可能会抛出异常了,我们需要导入数据的时候进行日志瘦身. 问1:导入数据的时候,瘦身是否会造成数据库的异常? DBA提供解决方案 ...
-
C语言常用的小代码
圆周率Pi tan(Pi/4)=1 => Pi=4*arctan(1) 反正切函数arctan()在C语言里表示为atan(),为保证精度取圆周率的代码如下: const double Pi = ...
-
Windows环境Mycat数据库分库分表中间件部署
下载地址MYCAT官方网站 jdk安装配置 首先去oracle官网下载并安装jdk8,添加环境变量,JAVA_HOME设置为D:\Worksoftware\Java\jdk1.8 CLASSPATH设 ...
-
【转】Cygwin访问Windows驱动器
From:http://www.cygwin.cn/site/info/show.php?IID=1000 由于自己的项目需要使用Linux内核,所以自己在windows下安装了一个Linux虚拟机! ...
-
Uubntu scrot 的简单使用
scrot 是一个使用 imlib2 库截取屏幕和保存图像的的工具. 1:安装 #apt-get install scrot 2:查看帮助 #scrot -help -v, --version显示版本 ...
-
随心测试_软测基础_004<;测试人员工作职责>;
接上篇续,依据_软测基础体系:<依据不同的测试对象,选取适合的方法,按照设计的流程完成测试工作,检验整个过程是否达到测试的目的>.“学以致用”,实践于工作职责 常见面试题: —— 诸如以下 ...