【编程课堂】震惊!小 bug 引发大灾难,0.1 + 0.2 的结果竟然是……

各位观众点进标题看文章的时候,我已经准备打包行李去UC报道啦~

冷笑话结束,嗯,说正事。

请大家思考一下在 python 控制台输入 0.1 + 0.2 == 0.3 ,返回的结果是什么?

手边有电脑的同学可以立即在 python 控制台下尝试一下,对浮点数精度不够了解的同学可能会大呼:天啦噜,夭寿啦,怎么会是 False

没错 ,不管是在 Python,还是 C++、Java、JavaScript 等其他语言中,都是 False

为什么会出现这样的结果?首先我们要了解,在计算机的存储类型为二进制,十进制的 0.1 与 0.2 在计算机中会已二进制的形式表示,规则如下:

十进制小数转换成二进制小数采用”乘2取整,顺序排列”法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数 部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。

以 0.1 为例,我们做一下转换:

步数

算式

结果

1

0.1 * 2 = 0.2

取 0

2

0.2 * 2 = 0.4

取 0

3

0.4 * 2 = 0.8

取 0

4

0.8 * 2 = 1.6

取 1

5

0.6 * 2 = 1.2

取 1

6

0.2 * 2 = 0.4

取 0

7

0.4 * 2 = 0.8

取 0

8

0.8 * 2 = 1.6

取 1

……

比较第二步和第六步,可以得知, 已二进制 表示 0.1 最终的结果为一个无限循环的数 0.0001100110011…… ,但由于计算机的存储位数是有限的,并不能存储一个无限循环的数。对于 Python 来说,浮点数有 53 位精度。为了把这个数存起来,必然会丢失部分精度,造成误差,所以最终的近似结果为:

0.00011001100110011001100110011001100110011001100110011010

同理, 对 0.2 的处理也是一样。所以当两个存在误差的数相加,其结果也必定会出现误差,这就解释了在计算机中为什么 0.1 + 0.2 不等于 0.3。

浮点数精度的知识远不止此,摊开来讲一本书也讲不完,所以对于初学者来说只要知道有这么回事就行了,之后再遇到就不要惊讶了,日常工作中可遵循以下准则:

  1. 尽量避免使用小数比较大小,比较两个小数是否相等时可写成 abs(a - b) < 0.000001。即两个数的差值足够小。
  2. 确保数组的索引都是整数。
  3. 按分(而不是元)计算金额。百分比放大100倍计算以避免出现小数。
  4. Python3 使用除法 / 时需注意,它的结果总是小数,整除的符号是 //
  5. 避免在同一个表达式中使用相差太大或太小的数值。将很小的数值和很大数值相加,小的数值很可能被当作 0。

当然,在某些科研、财务等对精度要求比较高的领域中,Python 提供 decimal 模块准确控制精度。

它具有以下特点:

  1. 提供十进制数据类型,并且存储为十进制数序列;
  2. 有界精度:用于存储数字的位数是固定的,可以通过 decimal.getcontext().prec=x来设定,不同的数字可以有不同的精度

可以通过整数、字符串或者元组构建 decimal.Decimal,对于浮点数需要先将其转换为字符串

>>>from decimal import *
# 设置精度为 7 位
>>>getcontext().prec = 7
>>>Decimal(1) / Decimal(7)
Decimal('0.1428571')

参考文章 Python官方对于浮点数的解释:https://docs.python.org/2/tutorial/floatingpoint.html decimal 模块:https://docs.python.org/2/library/decimal.html 代码之谜:http://justjavac.iteye.com/blog/1724438 Android计算器低级错误?都是二进制惹的祸!:http://www.guokr.com/article/27173/

原文发布于微信公众号 - Crossin的编程教室(crossincode)

原文发表时间:2017-03-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏趣谈编程

外部排序

当我们要排序的文件太大以至于内存无法一次性装下的时候,这时候我们可以使用外部排序,将数据在外部存储器和内存之间来回交换,以达到排序的目的

1600
来自专栏美团技术团队

Java8系列之重新认识HashMap

摘要 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8...

4325
来自专栏高爽的专栏

HashMap深度解析(二)

上一篇比较深入的分析了HashMap在put元素时的整体过程,Java Collections Framework中实际操作的都是数组或者链表,而我们通常...

2290
来自专栏程序员的SOD蜜

Why to do,What to do,Where to do 与 Lambda表达式!

最近我做一个“四象限”图表控件,其中有一个比较复杂的“坐标变换”问题,即是如何让一组数据放到有限的一个区间内,例如有一组数据 List[4,5,6,7,8],要...

2259
来自专栏.NET技术

UML类图

  这几天回顾了《大话设计模式》,发现看类图的时候分不清了,为了加强记忆,也方便回顾,自己手动画了一次类图,并参考了网上文章将此作为笔记,记录下来。

1574
来自专栏Fish

CUDA PTX ISA阅读笔记(二)

8. 第八章 指令集 这一章占了整个手册的一大半(百十来页吧),主要介绍各种指令,虽然页数很多,但是大多数指令都很简单。 8.1. 指令的形式和语义描述 这章就...

4765
来自专栏Jack-Cui

155.Min Stack(Stack-Easy)

    Design a stack that supports push, pop, top, and retrieving the minimum elem...

2135
来自专栏Python小屋

Python中else关键字的常见用法

Python中的else常见用法有三:选择结构、循环结构和异常处理结构。 (1)选择结构 这应该是最常见的用法,与关键字if和elif组合来使用,用来说明条件不...

29710
来自专栏窗户

Scheme来实现八皇后问题(2)

  上一章讲了用1~n的排序来表示n皇后的解,然后通过枚举1~n所有的排列、判定谓词过滤所有排列得到最终的所有解。

1343
来自专栏数据处理

mat(矩阵)与array(数组)区别

2503

扫码关注云+社区

领取腾讯云代金券