惊!0.1+0.2还要用电脑算,程序员都是小学没毕业?

152 阅读2分钟

如果你问一个小学生 0.1+0.2等于多少,我相信他会毫不犹豫地告诉你等于0.3。但是如果你去问一个程序员,他就会很谨慎地告诉你,稍等一下,然后飞快地在电脑上敲下几行代码:

public class Demo {

public static void main(String[] args) {

System.out.println(0.1 + 0.2);

}

}

这时候你心里肯定在说:”这哥们小学没毕业?这种问题还要用电脑算?“ 就在你即将不屑地转身离开时,电脑上出现了计算结果:

0.30000000000000004

”什么鬼?我读书少你别骗我,一定是电脑坏了!“

![](https://upload-images.jianshu.io/upload_images/24762785-55847775eaa85d1c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

...

![](https://upload-images.jianshu.io/upload_images/24762785-290d69d38194b87c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

重复计算得到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

![](https://upload-images.jianshu.io/upload_images/24762785-d185c02b509aafd9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

现在可以将尾数相加了:

0.1100110011001100110011001100110011001100110011001101

  • 1.1001100110011001100110011001100110011001100110011010

10.0110011001100110011001100110011001100110011001100111

尾数计算结果规格化处理,向右移一位(最后一位1舍去),同时将指数+1得到双精度计算结果

0 01111111101 (1.) 0011001100110011001100110011001100110011001100110011(1)

由于右规了一位1,因此需要进行+1预算

0011001100110011001100110011001100110011001100110011

  • 0000000000000000000000000000000000000000000000000001

0011001100110011001100110011001100110011001100110100

最终得到的结果为:

0 01111111101 0011001100110011001100110011001100110011001100110100

转为10进制为:

![](https://upload-images.jianshu.io/upload_images/24762785-0519525d09ef6125.gif?imageMogr2/auto-orient/strip)

0.30000000000000004440892098500626

这就是0.1+0.2不等于0.3的原因所在。