关于0.1+0.2≠0.3的问题
发现问题
啥也不说,我们先看一段python代码的执行情况:
1+2=3,0.1+0.3=0.4没毛病,但是:0.1+0.2=0.30000000000000004,什么鬼???
很多老猿估计都清楚是啥情况,但还有些小伙伴可能不太了解,不管怎样,还是要给大家娓娓道来。
我们都知道,在计算机中所有的数据最终都会被转化为0和1进行存储,所以我们需要清楚以下两点问题:
-
浮点数如何转化为二进制
-
浮点数的二进制如何存储
浮点数如何转化为二进制
首先我们要了解浮点数的二进制表示,有以下两个原则:
-
整数部分对2取余然后逆序排列
-
小数部分乘2取整数部分,然后顺序排列
接下来我们来看看0.1的二进制表示,由于0.1的整数部分是0,所以直接计算小数部分即可。下面我们来看小数部分的计算,按照“小数部分乘2取整数部分,然后顺序排列”的计算方式:
0.1*2 = 0.2 //整数部分为0,所以小数点后第一位为0
0.2*2 = 0.4 //整数部分为0,所以小数点后第二位为0
0.4*2 = 0.8 //整数部分为0,所以小数点后第三位为0
0.8*2 = 1.6 //整数部分为1,所以小数点后第四位为1
0.6*2 = 1.2 //整数部分为1,所以小数点后第五位为1 (整数部分不在乘2的范围)
0.2*2 = 0.4 //整数部分为0,所以小数点后第6位为0
……
按这种方式计算你会发现,0.1的二进制表示是0.00011001100110011001100110011……0011,0011作为二进制小数的循环数进行无限循环。这是不是就意味着你永远都存不下0.1的二进制小数。所以引出第二个问题:浮点数的二进制如何存储。
浮点数的二进制如何存储
简要介绍下IEEE754浮点格式:它采用科学计数法以底数为2的小数来表示浮点数。单精度浮点数(32位)用1个bit作为符号位表示正数还是负数,用8个bit表示指数,剩下的23个bit用来表示尾数(即小数部分)。此处指数用移码存储,尾数则是原码(没有符号位)。双精度浮点数(64位)最高位第1个bit为用来作为符号位,然后顺下的11个bit来存储指数部分,最后,剩下的52个bit存储尾数部分。
下图为64位浮点数的存储示意图:
回到最初的问题
我们回到问题:为什么0.1+0.2=0.30000000000000004?首先声明一点:这不是bug,这是十进制与二进制的转换导致的精度问题!就好比十进制无法精确表示1/3(0.33333333……)一样,二进制也有无法精确表示的值。其次,这个问题出现在很多的编程语言中,如:c/c++、java、js、python。准确的说:采用了IEEE754规范来存储浮点数的任何编程语言都有这个问题。下面我们分析下整个运算过程:
a. 0.1的二进制表示为:
1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-4;
b. 按64bit的存储空间以及IEEE 754 Floating-point采用round to nearest, tie to
even的舍入模式计算,0.1实际存储时的位模式是0-01111111011-1001100110011001100110011001100110011001100110011010;c. 0.2的二进制表示为 :
1.1001100110011001100110011001100110011001100110011001 1(0011)+ * 2^-3;
d.同样的计算方式,0.2实际存储时的位模式是0-01111111100-1001100110011001100110011001100110011001100110011010;
e. 实际存储的位模式作为操作数进行浮点数加法,得到
0-01111111101-0011001100110011001100110011001100110011001100110100,转换为十进制即为0.30000000000000004。
原来0.30000000000000004是这么来的。借用虎扑名言:我好了!不知道在屏幕前的您“好了吗”?
到文末了,在这里出一道题:1.1+2.2等于多少?您可以用python运行试试看,提前告诉您,结果既不是3.3也不是3.30000000000000004,很有意思哦,试试吧!