0.1 + 0.2 == 0.3 ?

61 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情

导言

这是个经典的面试题,主要考察我们对十进制/二进制间的转换和浮点数及它对应的各语言下标准的理解。虽然你知道它们肯定是不相等的,但对具体的计算过程原理可能是一知半解的吧?我之前是这样的。

浮点数

我使用的是JavaScript,大家都知道,它采用的是双精度浮点数类型,也就是float64。我们设想一下,如果仅用这64比特存储想要表示的数字,即使不涉及到符号位和小数位,虽然数字很大,但终究有限。这也是定点数表示法的局限所在。我们需要的是既能表示很大的数,也能表示很小的数,联想到我们常用的十进制,大家就能想到科学计数法。刚才也说了双精度浮点数总共是64比特,由于我们不仅要表示小数,还要表示正负数,所以就需要符号位s表示正负,指数位e表示很大的数或很小的数和有效数位f,它们分别占用多少比特呢?就需要一个规范,这就是我们常见的IEEE754标准,它定义:

sef
1bit11bit52bit

指数e:

需要注意,指数e有11比特,表示的范围是0 ~ 2047(都包括在内),由于实际计算中它可能会当做负数计算,所以它与真实数是个映射关系,0和2047是有特殊意义,不在映射范围之内,其中的12046分别映射的是-10221023.

计算

好了,理论就到这里,下面我们来计算:

0.1怎么转化成二进制呢?方法是:

不断乘2,小于1,记0,继续乘;大于1,记1,用大于1的部分继续乘,直到等于1,记1结束。具体过程如下:

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

0.6 * 2 = 1.2 记 1,余0.2


0.2 * 2 = 0.4 记 0

0.4 * 2 = 0.8 记 0

0.8 * 2 = 1.6 记 1,余0.6

0.6 * 2 = 1.2 记 1,余0.2

看到了吗? 0011...不断循环,说明0.1计算机在有限位数的情况下是不能精确表示其值的。取前52位:
0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 010

注:被截取掉的部分首位是1,则进位。

同样的方法计算0.2:

0.2 * 2 = 0.4 记 0

0.4 * 2 = 0.8 记 0

0.8 * 2 = 1.6 记 1,余0.6

0.6 * 2 = 1.2 记 1,余0.2


0.2 * 2 = 0.4 记 0

0.4 * 2 = 0.8 记 0

0.8 * 2 = 1.6 记 1,余0.6

0.6 * 2 = 1.2 记 1,余0.2

计算结果是:
0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011

0.1+0.2加法计算:
0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011

结果:
0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101

转化成IEEE754标准格式:
(-1)^0 * 1.00 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1101 * 2^1021

转化成十进制结果:0.30000000000000004.

参考

IEEE754标准