为什么 0.1 + 0.2 不等于 0.3 ?

十进制小数在高级程序设计语言中属于浮点类型数据,既然十进制小数存储到内存中有误差,所以计算机中的浮点型数值的计算是不精确的,这种问题 对于所有支持浮点数运算的编程语言都是存在的

先看两个简单但诡异的代码:

1
2
0.1 + 0.2 > 0.3 // true
0.1 * 0.1 = 0.010000000000000002

0.10.2 为什么就不等于 0.3 昵?要回答这个问题,得先了解计算机内部是如何表示数的。


内存中数的存储

我们知道,计算机中任何类型的数据,其本质是以二进制的形式存储的。

[1] –> 计算机内部如何表示整数

这里以十进制数 13 来展示 “按位计数法” 如何表示整数:

十进制值 进制 按位格式 描述
13 10 13 1x10^1 + 3x10^0 = 10 + 3
13 2 1101 1x2^3 + 1x2^2 + 0x2^1 + 1x2^0 = 8 + 4 + 0 + 1

[2] –> 计算机内部如何表示小数

再来看,十进制小数 0.625 怎么用 “按位计数法” 表示:

十进制值 进制 按位格式 描述
0.625 10 0.65 6x10^-1 + 2x10^-2 + 5x10^-3 = 0.6 + 0.02 + 0.005
0.65 2 0.101 1x2^-1 + 0 x2^-2 + 1x2^-3 = 1/2 + 0 + 1/8

[3] –> 如何用二进制表示 0.1

最终回归到本篇开头的样例,先来看如何用二进制表示 0.1 ???

我们知道,十进制整数转二进制方法:除 2 取余,逆序排列;十进制小数转二进制方法:乘 2 取整,顺序排列。

十进制 0.1 转换成二进制,乘 2 取整过程:

1
2
3
4
5
6
7
8
0.1 * 2 = 0.2 # 0
0.2 * 2 = 0.4 # 0
0.4 * 2 = 0.8 # 0
0.8 * 2 = 1.6 # 1
0.6 * 2 = 1.2 # 1
0.2 * 2 = 0.4 # 0

.....

从上面可以看出,0.1 的二进制格式是:0.000110011.... 。这是一个二进制无限循环小数,但计算机内存有限,我们不能用储存所有的小数位数。

也就是说 >>>> 将十进制小数转化为其它进制小数时,存在无限问题。 >>>> 那么在精度与内存间如何取舍呢?

答案是:在某个精度点直接舍弃。

当然,代价就是,0.1 在计算机内部根本就不是精确的 0.1,而是一个有舍入误差的 0.1。当代码被编译或解释后,0.1 已经被四舍五入成一个与之很接近的计算机内部数字,以至于 计算还没开始,一个很小的舍入错误就已经产生了,并且这种误差会在后续的计算过程中不断累计。

这也就是 0.1 + 0.2 不等于 0.3 的根本原因。

有误差的两个数,其计算的结果,很可能 与我们期望的不一样了。


注意前面的这句话中的 “很可能” 这三个字?为啥是很可能昵?

0.1 + 0.1 为什么等于 0.2 ?

答案是:两个有舍入误差的值在求和时,相互抵消了,但这种“负负得正,相互抵消”不一定是可靠的,当这两个数字是用不同长度数位来表示的浮点数时,舍入误差可能不会相互抵消。

又如,对于 0.1 + 0.3 ,结果其实并不是 0.4,但 0.4 是最接近真实结果的数,比其它任何浮点数都更接近。许多语言也就直接显示结果为 0.4 了,而不展示一个浮点数的真实结果了。

另外要注意,二进制能精确地表示位数有限且分母是 2 的倍数的小数。比如 0.5(0.25、0.625…),0.5 在计算机内部就没有舍入误差。所以 0.5 + 0.5 === 1


胡乱舍入,能满足所有的计算需求吗

我们看两个现实的场景:

  • 对于一个修建铁路的工程师而言,10 米宽,还是 10.0001 米宽并没有什么不同。铁路工程师就不需要这么高 0.x 这样的精度;
  • 对于芯片设计师,0.0001 米就会是一个巨大不同,他也永远不用处理超过 0.1 米距离。

不同行业,要求的精度不是线性的,我们允许(对结果无关紧要的)误差存在。 10.000110.001 在铁路工程师看来都是合格的。

虽然允许误差存在,但在使用浮点数进行计算或逻辑处理时,不注意,就可能出问题。记住,永远不要直接比较两个浮点的大小。


Author

Waldeinsamkeit

Posted on

2016-07-29

Updated on

2022-12-02

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.