如果你问一个小学生 0.1+0.2等于多少,我相信他会毫不犹豫地告诉你等于0.3。但是如果你去问一个程序员,他就会很谨慎地告诉你,稍等一下,然后飞快地在电脑上敲下几行代码:
public class Demo {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
}
}
这时候你心里肯定在说:”这哥们小学没毕业?这种问题还要用电脑算?“ 就在你即将不屑地转身离开时,电脑上出现了计算结果:
0.30000000000000004
”什么鬼?我读书少你别骗我,一定是电脑坏了!“
OK,电脑没有坏,那强大的电脑和聪明绝顶的程序员为什么比不上一个小学生呢?我们来一起分析一下:
首先我们来复习一下义务教育阶段学习的一个重要知识点:科学计数法
把一个数表示成a与10的n次幂相乘的形式(1≤|a|<10,n为整数)
我们都知道计算机中的数据都是以二进制形式来表示的,在内存中有两种存储方式:
float(单精度)1位符号位8位指数位23位尾数位
double(双精度)1位符号位11位指数位52位尾数位
首先将十进制0.1转换为二进制,十进制小数转二进制一般采用乘2取整法:
0.1 x 2 = 0.2 整数部分为 0 小数部分继续 x 2
0.2 x 2 = 0.4 整数部分为 0 小数部分继续 x 2
0.4 x 2 = 0.8 整数部分为 0 小数部分继续 x 2
0.8 x 2 = 1.6 整数部分为 1 小数部分继续 x 2
0.6 x 2 = 1.2 整数部分为 1 小数部分继续 x 2
0.2 x 2= 0.4 整数部分为 0 小数部分继续 x 2
...
重复计算得到64位二进制小数:
0.0001100110011001100110011001100110011001100110011001100110011001
以二进制科学计数法来表示,也就是a与2的n次幂相乘的形式(1≤|a|<2,n为整数),a就是尾数,n就是指数,即
1. 100110011001100110011001100110011001100110011001100110011001 * 2 ^ -4
先来处理尾数,JAVA浮点数默认为双精度,使用52位存储尾数,由于二进制的科学计数法整数位永远都是1,因此可以直接不存储,截取小数部分前52位为尾数位(0舍1入)
1001100110011001100110011001100110011001100110011010
再来处理指数,由于指数位为11位,因此指数偏移值为2 ^ 10 -1 = 1023, 指数位实际值 x - 1023 = -4, 得到x为1019,转为二进制1111111011, 高位补0,满11位,得01111111011
正数符号位为0,得到0.1的双精度二进制表示为:
0 01111111011 (1.)1001100110011001100110011001100110011001100110011010
同理,得到0.2的双精度二进制表示为:
0 01111111100 (1.)1001100110011001100110011001100110011001100110011010
在相加之前将0.1的指数调整和0.2的指数相同(即-4调为-3),同时将尾数右移一位得到(0.)11001100110011001100110011001100110011001100110011010,根据0舍1入原则将最后一位0舍去得到
0 01111111100 (0.)1100110011001100110011001100110011001100110011001101
现在可以将尾数相加了:
0.1100110011001100110011001100110011001100110011001101
+ 1.1001100110011001100110011001100110011001100110011010
10.0110011001100110011001100110011001100110011001100111
尾数计算结果规格化处理,向右移一位(最后一位1舍去),同时将指数+1得到双精度计算结果
0 01111111101 (1.) 0011001100110011001100110011001100110011001100110011(1)
由于右规了一位1,因此需要进行+1预算
0011001100110011001100110011001100110011001100110011
+ 0000000000000000000000000000000000000000000000000001
0011001100110011001100110011001100110011001100110100
最终得到的结果为:
0 01111111101 0011001100110011001100110011001100110011001100110100
转为10进制为:
0.30000000000000004440892098500626
这就是0.1+0.2不等于0.3的原因所在。