0.1 + 0.2 = ?

即使是没上过学的人,也知道答案是0.3,但是,在看似无所不能,计算速度远超人类的计算机来说,答案就不是0.3了。看看chrome下的结果:

1
2
3
0.1 + 0.2

0.30000000000000004

再看看python:

1
2
3
4
5
6
Python 2.7.10 (default, Jul 30 2016, 18:31:42) 
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.1 + 0.2
0.30000000000000004
>>>

再试试C语言:

1
2
3
4
5
6
7
8
#include "stdio.h"

int main(){

double a = 0.1 + 0.2;
printf( "%.17lf", a );

}
1
0.30000000000000004

qustion

产生原因

对于数字的存储绝大多数语言遵循的都是IEEE 754规范,简单来说就是一些革命先烈告诉你当你在二进制计算机中想要一个浮点数的时候应该如何去表达。

首先,内存是有限的,所以一个数字,当它声明好了之后所占的位数是固定的,比如在C语言中,double类型所占的字节数是8,那么总共就是64位(可能某些奇葩编译器会有些差异),革命先烈们是怎么规定的呢?

double

有一个通用的计算公式:

float-cal

  1. (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
  2. M表示有效数字,大于等于1,小于2。
  3. 2^E表示指数位。

举例来说,十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。

有几点需要注意:

  • 因为有效数字M的第一位必然是1,所以存储的时候就把这一位舍去了,可以空出一位用来存储,在计算的时候加回来就行了。

  • 指数E没有符号位,无法表示负数,所以在计算的时候减去一个半数,比如:double是64位,11位用来存储指数,假设你要保存指数10,那么就必须存储1023 + 10,也就是10000001001。ps: 以上讨论的都是64位浮点数,如果是float,也就是32位的浮点数,指数位数是8,保存指数10就是:127 + 10

对于指数E,还有一下三条更细的规则:

1
2
3
(1)E不全为0或不全为1。这时,指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
(2)E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
(3)E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。

好了,说完了背景,那么就可以来看看到底是什么原因了。

整数的二进制和十进制之间的相互转换,相信大家都会算,小数部分,很多人可能已经忘记如何计算了。

总体来说就是 乘二取整,比如0.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.4 * 2
0.8 >> 0
0.8 * 2
1.6 >> 1
......

0.1 变成了 0.0001 1001 1001 1001 .... 1001无限循环
0.2 变成了 0.0011 0011 0011 0011 .... 0011无限循环

对于计算机来说,0.1和0.2的二进制就变成了一个无尽数,其实大部分小数转换成二进制都是无穷数。于是,二进制的0.1和0.2就变成了:

竖线后面代表存储位数之外

1
2
0.0001 1001 1001 ...... 1001 | 1001   
0.0011 0011 0011 ...... 0011 | 0011

二进制相加之后变成了:

1
...... 1100 | 1100

由于存储位数限制,必须舍去竖线后面的有效数字,按照舍0进1的方式,就变成了 .... 1101,所以整个二进制数字就变成了:

1
0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101

转换成十进制就是:

1
0.30000000000000004440892098500626

就变成了我们最初所看到的结果。

至此,我们开头的问题已经有了答案,那么下面有几个引申问题:

  • JS中类型存储位是52位,那么可以准确表达最大的数字就是:Math.pow(2, 53), 也就是:9007199254740992,那么以下几个表达式的值是什么呢?

9007199254740992 + 1
9007199254740992 + 2
9007199254740992 + 3
9007199254740992 + 4

  • 0.1, 0.2, 0.3, 0.4 转换成二进制小数都是无穷的,那么以下表达式的结果如何呢?

0.1 + 0.2 === 0.3
0.1 + 0.3 === 0.4
0.2 + 0.4 === 0.6
0.2 + 0.3 === 0.5