python 精度损失_Python的浮点数损失精度问题

本篇讨论的现象可以从下面这段脚本体现出来:

>>> 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 离的很近。因此一般我们可以通过这个关系来近似推导精度关系。

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

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

相关文章

锐捷交换机实验案例:vlan间互访的配置与验证

组网需求&#xff1a; 1、如下图所示&#xff0c;某用户内网被划分为VLAN 10、VLAN 20、VLAN 30&#xff0c;以实现相互间的2 层隔离&#xff1b; 2、3 个VLAN 对应的IP 子网分别为192.168.10.0/24 、192.168.20.0/24 、192.168.30.0/24&#xff0c;3 个VLAN 通过3 层核心交换机…

mysql innodb隔离级别_浅析MySQL InnoDB的隔离级别

本文就将对上面这两个问题进行解答&#xff0c;分析事务的隔离级别以及相关锁机制。隔离性简介隔离性主要是指数据库系统提供一定的隔离机制&#xff0c;保证事务在不受外部并发操作影响的"独立"环境执行&#xff0c;意思就是多个事务并发执行时&#xff0c;一个事务…

sql 2008服务器内存一直居高不下_经验之谈:内存问题造成数据库性能异常怎么破?...

作者&#xff1a;罗贵林原文链接&#xff1a;https://mp.weixin.qq.com/s/2e5eKSoGlU9J4Rjq1zwLnw导读&#xff1a;在使用数据库的过程中&#xff0c;内存不足常常会引起数据库异常。但是内存不足&#xff0c;又会为数据库带来哪些具体的影响呢&#xff1f;本次&#xff0c;我们…

mybatis 打印SQL语句

方法一&#xff1a; 在mybatis-config.xml中配置加一个setting <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd&quo…

mysql 字符串匹配函数_mysql 自定义函数 实现字符串匹配

先来一个截图&#xff1a;fSearch函数的第一个参数为单一字符串(即 没有特殊字符串隔开)fSearch函数的第一个参数非单一字符串多个字符串同样可以匹配。函数代码&#xff1a;DELIMITER $$Create function fSearch(targetStr VARCHAR(100),findStr VARCHAR(100)) RETURNS INTBEG…

stm32时钟树_先学STM8,还是学STM32?

有朋友问&#xff1a;我学习过51&#xff0c;接下来我是先学习STM8&#xff0c;还是STM32呢&#xff1f;物联网STM32入门 - 直播课程 - 创客学院​www.makeru.com.cn嵌入式开发直播课 - STM32 USART串口的应用 - 创客学院直播室​www.makeru.com.cn1、写在前面想要明白这个问题…

如何使用django显示一张图片

django显示图片对新手来说真的算是一个坑。。 这里记录下小白爬坑的历程。 首先&#xff0c;你需要一个可以运行的django服务器&#xff0c;能显示正常的html文本&#xff0c;无法显示图片 这是html的文本&#xff0c;可以显示文字&#xff0c;无法显示图片 <h1>An Image…

mysql创建时间字段6_mysql 时间字段介绍

mysql时间类型大概有5种&#xff0c;如下图1、创建数据库create table t1 (id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,d1_data date,d2_time time,d3_datatime datetime,d4_year year,d5_timestamp TIMESTAMP);字符串方式插入INSERT INTO t1 (d1_data,d2_time,d3_datati…

python课设代码_python课程编程题汇总(上)

python编程题汇总众所周知&#xff0c;由于疫情的原因&#xff0c;大家都在上网课&#xff0c;我也不例外啦~用这个贴子来记录也和大家分享一下我们课上的编程讨论题中篇在此→python课程编程题汇总(中)下篇在此→python课程编程题汇总(下)1、货币转换编写货币转换程序描述&…

安装Python 3.6 在Ubuntu 16.04 LTS 版本

在ubuntu 16.04版本中&#xff0c;系统默认安装 了python 2.7和3.5版本&#xff0c;因为系统本身用到python的程序&#xff0c;删除默认的版本又担心系统有问题&#xff0c;那有没有办法同时在安装和使用python 3.6版本呢&#xff1f;下文将一起安装python 3.6并修改原系统的py…

matlab动画_弹簧振子振动的matlab动画演示

用Matlab阐述物理中的胡克定律&#xff0c;为弹簧振子震动的matlab动画示意图&#xff0c;另有一个为不同质量不同弹簧系数的比较。spring.m,compare.mrectangle(position,[12,8.5,2,0.3],FaceColor,[0.5,0.3,0.4]); axis([0,15,-1,10]); hold on plot([13,13],[7,8.5],r,li…

svn合并分支到主干_谈谈代码分支管理

前言从2019年上半年云音乐的客户端团队开始迁移到双周迭代后&#xff0c;随之而来的是我们需要重新调整代码分支的管理方法&#xff0c;来应对开发流程的变更。双周迭代顾名思义一周开发一周测试&#xff0c;目的就是为了快速交付。纵观整个开发流程&#xff0c;我们需要在两周…

ctf实验平台-成绩单

题目链接&#xff1a;http://120.24.86.145:8002/chengjidan/ 平台地址&#xff1a;http://123.206.31.85/ 第一步&#xff1a;暴库 id-1 union select 1,2,3,group_concat(schema_name) from information_schema.schemata# 第二步&#xff1a;爆表 id-1 union select 1,2,3,ta…

python 扫描仪_玩《Minecraft我的世界》学python编程,可领|取电子学习版本

为何选择学习pythonpython是一种解释型、面向对象、动态数据类型的高级程序设计语言&#xff0c;它具有丰富和强大的库&#xff0c;能够把其它语言&#xff08;尤其是c&#xff09;制作的各种模块很轻松地联结在一起。pyton在编程语言排行榜中高居首位。[求抱抱]编程听起来很高…

python识别虚假新闻的分类器_使用NLP检测和对抗AI生成的假新闻

作者|MOHD SANAD ZAKI RIZVI编译|VK来源|Analytics Vidhya概述由AI生成的假新闻(神经假新闻)对于我们的社会可能是一个巨大的问题本文讨论了不同的自然语言处理方法&#xff0c;以开发出对神经假新闻的强大防御&#xff0c;包括使用GPT-2检测器模型和Grover(AllenNLP)每位数据科…

vue中使用导出表格功能

1.下载依赖 npm install -S file-saver xlsxnpm install -D script-loader 2.在src下创建vendor文件夹&#xff0c;并在文件夹中放两个文件 Blob.js (function (view) {"use strict";view.URL view.URL || view.webkitURL;if (view.Blob && view.URL) {try …

adb shell 书籍_开发必备---你应该知道的一些 ADB 命令

版权声明&#xff1a;本文为LooperJing原创文章&#xff0c;转载请注明出处&#xff01;一、设备相关1、adb devices显示连接到计算机的设备List of devices attachedbe34d81e device输出格式为 [serialNumber] [state]&#xff0c;state 有如下几种&#xff1a;列名解释nodevi…

Angular Reactive Forms -- Model-Driven Forms响应式表单

Angular 4.x 中有两种表单&#xff1a; Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 ) 官方文档&#xff1a;https://v2.angular.cn/docs/ts/latest/guide/forms.html Reactive Forms (Model-Driven Forms) - 响应式表单 官方文档&#xff1a; …

python实现守护进程_守护进程原理及Python实现

守护进程原理及Python实现守护进程&#xff0c;不依赖于终端&#xff0c;在后台运行的程序&#xff0c;通常称为daemon(ˈdiːmən或ˈdeɪmən)。一些常见的Linux软件通常都是已守护进程的方式运行&#xff0c;比如&#xff1a;nginxredismemcached守护进程的原理&#xff1a;…

python生成器迭代_二十、深入Python迭代器和生成器

「Author&#xff1a;Runsen」学习python的过程中&#xff0c;迭代器与生成器是绕不开的话题&#xff0c; 什么是迭代器和生成器呢&#xff1f;下面我们来了解一下什么是迭代。但在了解迭代器之前&#xff0c;首先需要知道什么是容器。容器正所谓&#xff1a;一切都是对象&…