14. 浮点数运算:问题和局限¶

浮点数在计算机硬件中表示为以 2 为底(二进制)的小数。例如,十进制小数

0.125

是1/10 + 2/100 + 5/1000 的值,同样二进制小数

0.001

是 0/2 + 0/4 + 1/8 的值。这两个小数具有相同的值,唯一真正的区别是,第一个小数是十进制表示法,第二个是二进制表示法。

不幸的是,大多数十进制小数不能完全用二进制小数表示。结果是,一般情况下,你输入的十进制浮点数仅由实际存储在计算机中的近似的二进制浮点数表示。

这个问题在十进制情况下很容易理解。考虑分数 1/3,你可以用十进制小数近似它:

0.3

或者更接近的

0.33

或者再接近一点的

0.333

等等。无论你愿意写多少位数字,结果永远不会是精确的 1/3,但将会越来越好地逼近 1/3。

同样地,无论你使用多少位的二进制数,都无法确切地表示十进制值 0.1。1/10 用二进制表示是一个无限循环的小数。

0.0001100110011001100110011001100110011001100110011...

在任何有限数量的位停下来,你得到的都是近似值。

在一台运行 Python 的典型机器上, Python 浮点数具有 53 位的精度,所以你输入的十进制数0.1存储在内部的是二进制小数

0.00011001100110011001100110011001100110011001100110011010

非常接近,但不完全等于 1/10。

由于解释器显示浮点数的方式,很容易忘记存储在计算机中的值是原始的十进制小数的近似。Python 只打印机器中存储的二进制值的十进制近似值。如果 Python 要打印 0.1 存储的二进制的真正近似值,将会显示

>>>0.1

0.1000000000000000055511151231257827021181583404541015625

这么多位的数字对大多数人是没有用的,所以 Python 显示一个舍入的值

>>>0.1

0.1

意识到在真正意义上这是一种错觉是很重要的:机器中的值不是精确的 1/10,它显示 的只是机器中真实值的舍入。一旦你用下面的数值进行算术运算,这个事实就变得很明显了

>>>0.1 + 0.2

0.30000000000000004

注意,这是二进制浮点数的自然性质:它不是 Python 中的一个 bug ,也不是你的代码中的 bug。你会看到所有支持硬件浮点数算法的语言都会有这个现象(尽管有些语言默认情况下或者在所有输出模式下可能不会显示 出差异)。

还有其它意想不到的。例如,如果你舍入2.675到两位小数,你得到的是

>>>round(2.675, 2)

2.67

内置round()函数的文档说它舍入到最接近的值,rounding ties away from zero。因为小数 2.675 正好是 2.67 和 2.68 的中间,你可能期望这里的结果是(二进制近似为) 2.68。但是不是的,因为当十进制字符串2.675转换为一个二进制浮点数时,它仍然被替换为一个二进制的近似值,其确切的值是

2.67499999999999982236431605997495353221893310546875

因为这个近似值稍微接近 2.67 而不是 2.68,所以向下舍入。

如果你的情况需要考虑十进制的中位数是如何被舍入的,你应该考虑使用decimal模块。顺便说一下,decimal模块还提供了很好的方式可以“看到”任何 Python 浮点数的精确值。

>>>from decimal import Decimal

>>>Decimal(2.675)

Decimal('2.67499999999999982236431605997495353221893310546875')

另一个结果是,因为 0.1 不是精确的 1/10,十个值为 0.1 数相加可能也不会正好是 1.0:

>>>sum = 0.0

>>>for i in range(10):

... sum += 0.1

...

>>>sum

0.9999999999999999

二进制浮点数计算有很多这样意想不到的结果。“0.1”的问题在下面”误差的表示”一节中有准确详细的解释。更完整的常见怪异现象请参见浮点数的危险。

最后我要说,“没有简单的答案”。也不要过分小心浮点数!Python 浮点数计算中的误差源之于浮点数硬件,大多数机器上每次计算误差不超过 2**53 分之一。对于大多数任务这已经足够了,但是你要在心中记住这不是十进制算法,每个浮点数计算可能会带来一个新的舍入错误。

虽然确实有问题存在,对于大多数平常的浮点数运算,你只要简单地将最终显示的结果舍入到你期望的十进制位数,你就会得到你期望的最终结果。关于如何精确控制浮点数的显示请参阅格式化字符串的语法中str.format()方法的格式说明符。

14.1. 二进制表示的误差¶

这一节将详细解释“0.1”那个示例,并向你演示对于类似的情况自已如何做一个精确的分析。假设你已经基本了解浮点数的二进制表示。

二进制表示的误差 指的是这一事实,一些(实际上是大多数) 十进制小数不能精确地用二进制小数表示。这是为什么 Python(或者 Perl、 C、 C++、 Java、 Fortran和其他许多语言)通常不会显示你期望的精确的十进制数的主要原因:

>>>0.1 + 0.2

0.30000000000000004

这是为什么?1/10 和 2/10 不能用二进制小数精确表示。今天(2010 年 7 月)几乎所有的机器都使用 IEEE-754 浮点数算法,几乎所有的平台都将 Python 的浮点数映射成 IEEE-754“双精度浮点数”。754 双精度浮点数包含 53 位的精度,所以输入时计算机努力将 0.1 转换为最接近的 J/2**N 形式的小数,其中J 是一个 53 位的整数。改写

1 / 10 ~= J / (2**N)

J ~= 2**N / 10

回想一下 J 有 53 位(>= 2**52但<2**53),所以 N 的最佳值是 56:

>>>2**52

4503599627370496

>>>2**53

9007199254740992

>>>2**56/10

7205759403792793

即 56 是 N  保证 J 具有 53 位精度的唯一可能的值。J 可能的最佳值是商的舍入:

>>>q, r = divmod(2**56, 10)

>>>r

6

由于余数大于 10 的一半,最佳的近似值是向上舍入:

>>>q+1

7205759403792794

因此在 754 双精度下 1/10 的最佳近似是 J 取大于 2**56 的那个数,即

7205759403792794 / 72057594037927936

请注意由于我们向上舍入,这其实有点大于 1/10;如果我们没有向上舍入,商数就会有点小于 1/10。但在任何情况下它都不可能是精确的 1/10!

所以计算机从来没有”看到”1/10: 它看到的是上面给出的精确的小数,754 双精度下可以获得的最佳的近似了:

>>>.1 * 2**56

7205759403792794.0

如果我们把这小数乘以 10**30,我们可以看到其(截断后的)值的最大 30 位的十进制数:

>>>7205759403792794 * 10**30 // 2**56

100000000000000005551115123125L

也就是说存储在计算机中的精确数字约等于十进制值 0.100000000000000005551115123125。以前 Python 2.7 和 Python 3.1 版本中,Python 四舍五入到 17 个有效位,给出的值是 ‘0.10000000000000001’。在当前版本中,Python 显示一个最短的十进制小数,它会正确舍入真实的二进制值,结果就是简单的‘0.1’。

python浮点数运算问题_python基础教程之. 浮点数运算:问题和局限相关推荐

  1. python爬虫思路流程_python基础教程之【Python爬虫】爬了七天七夜,终|python基础教程|python入门|python教程...

    https://www.xin3721.com/eschool/pythonxin3721/ 起因 为了督促自己更加积极地写博客,我希望有一个排名系统能让我看到自己的进步.但是博客园对用户的排名体系相 ...

  2. python浮点数的范围和精度_python基础教程之C#教程之C#中float的取值范围和精度分析...

    https://www.xin3721.com/eschool/pythonxin3721/ 本文实例分析了C#中float的取值范围和精度.分享给大家供大家参考.具体分析如下: float类型的表现 ...

  3. python设置mysql外键_python基础教程之MySQL数据库之-foreign key 外键(一

    今日重点:外键 一对多 多对多 一对一 -------------------------------------------------------------------------------- ...

  4. python入门之字符串处理_python基础教程之python字符串处理方法|python基础教程|python入门|python教程...

    https://www.xin3721.com/eschool/pythonxin3721/ 字符和字符串可以用来相加来组合成一个字符串输出: 字符或字符串复制输出. 二.Extract &S ...

  5. python set 原理_Python基础教程之dict和set

    1. dict Python中的dict等于js中的 map ,使用键-值(key-value)存储,具有极快的查找速度. 如果 我们要根据同学的姓名去查找他的成绩在不用dict的情况下.就需要两个l ...

  6. python零基础入门建模_python基础教程之Python 建模步骤|python基础教程|python入门|python教程...

    #%%#载入数据 .查看相关信息 importpandas as pdimportnumpy as npfrom sklearn.preprocessing importLabelEncoderpri ...

  7. python爬取bilibili数据_python基础教程之selenium+phantomjs爬取bilibili

    selenium+phantomjs爬取bilibili 首先我们要下载phantomjs 你可以到 http://phantomjs.org/download.html 这里去下载 下载完之后解压到 ...

  8. python2.7使用教程_Python 2.7基础教程之:概要介绍

    .. _tut-informal: ************************************************** An Informal Introduction to Pyt ...

  9. python可以处理多大的数据_科多大数据之Python基础教程之Excel处理库openpyxl详解...

    原标题:科多大数据之Python基础教程之Excel处理库openpyxl详解 科多大数据小课堂来啦~Python基础教程之Excel处理库openpyxl详解 openpyxl是一个第三方库,可以处 ...

最新文章

  1. 网站不同优化时段的优化方法介绍
  2. 彻底理解Intel FPGA时序约束---解决方案篇(二)
  3. 方法论、方法论——程序员的阿喀琉斯之踵
  4. [基础题] 6.(*)按如下要求编写Java程序: (1)编写一个接口:OneToN,只含有一个方法int dispose(int n)
  5. MM的Windows 7 登录密码忘记之后
  6. MySQL JDBC驱动程序如何处理准备好的语句
  7. Struts2初始化过程代码分析
  8. git获得当前分支url_笔记本拿出来!软件工程师必须要知道的Git命令语句大汇总...
  9. open vswitch常用操作
  10. Centos7——NFS(Network File System)服务
  11. solr学习篇(三) solr7.4 连接MySQL数据库
  12. 独辟蹊径品内核 轻松领悟读书高境界
  13. 机械工程专业英语复习
  14. 鸿蒙智慧屏安装apk,亲测华为智慧屏支持安装以下第三方软件,大家赶紧试试!...
  15. vmware服务器虚拟化 pdf,VMware数据中心服务器虚拟化解决方案模板_V0.pdf
  16. adb为Android的root方法,Android 实现永久性开启adb 的root权限
  17. ArcGIS的地理坐标系、大地坐标系
  18. E舞成名模拟器分析及下载地址
  19. html5 单页动画,超炫的动画效果单页网站
  20. 4、RDA8910(4GCAT1)CSDK二次开发:期待已久的ADC采集

热门文章

  1. 最新阿里内推Java后端面试题
  2. LVM逻辑卷容量的增减
  3. vs code golang插件记录
  4. hdu 4923 Room and Moor (单调栈+思维)
  5. iOS开发多线程篇—自定义NSOperation
  6. Port Forwarding Port Triggering
  7. 观咆哮有感——系统升级的疼
  8. mysql 分组 列转行,mysql 列转行以及岁月分组
  9. 高校c语言程序设计比赛,分秒必争,力争上游,计算机学院举办第八届C语言程序设计挑战杯...
  10. iphone打字怎么换行_库克扎心!12年iPhone老用户换机小米10 Pro,每天玩机七八小时...