笔者曾经面试被问到过0.1+0.2的结果是啥,初看这题你可能心中会想,难道不是0.3吗?但你肯定会觉得没那么简单,那今天我们就来探一探究竟。

1. JavaScript中数字的存储机制

在JavaScript中数字是以IEEE 754 双精度64位浮点数(需翻墙)来存储的,它的表示格式为:

(s) * (m) * (2 ^ e)

其中s表示符号位,m 表示尾数,占52位,e 表示指数,占11位,根据ECMAScript 5 规范,e 的范围是 [-1074, 971],这样可以得出 js 能表示的最大值为1 * (2^53 - 1) * (2^971) = 1.7976931348623157e+308,而这个值恰好是 Number.MAX_VALUE 的值;同理可以推出 js 能表示的大于 0 的最小值是1 * 1 * (2 ^ -1074) = 5e-324,这个值恰好是 Number.MIN_VALUE 的值。

这里你可能注意到了,m 是 52位,能表示的最大值是2 ^ 52 - 1,可这里为什么是 53 呢?这就涉及到了隐藏位,比如1 * 1.00111100 * 2 ^ -3 中,m 表示的是 1.00111100 中的小数部分“00111100”,整数部分1就是隐藏位,这也就是说,只要指数不全为 0,那么它的隐藏位就是1。这里顺便提一下,js 里整数可以被精确表示的范围是-2 ^ 53 + 1 ~ 2 ^ 53 - 1

2. 数字表示

现在我们回到这道题目来,我们知道在计算机中,数字都是以二进制存储的,所以我们要先将 0.1 和 0.2 转化成二进制,对于十进制转二进制,整数部分除二取余,倒序排列,小数部分乘二取整,顺序排列,所以

0.1 转化为二进制
0.0 0011 0011 0011 0011 0011 0011 ... (0011循环)

0.2 转化为二进制
0.0011 0011 0011 0011 0011 0011 0011 ... (0011循环)

然后我们用之前说过的IEEE 754 双精度64位浮点数(需翻墙)来表示:

// 0.1
e = -4;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)

// 0.2
e = -3;
m = 1.1001100110011001100110011001100110011001100110011010 (52位)

当然,这里的 m 指的是小数点后的52位,而小数点前的整数部分1就是前面说过的隐藏位。

然后我们把它相加,这里有一个问题,就是指数不一致时,应该怎么处理,一般是往右移,因为即使右边溢出了,损失的精度远远小于左移时的溢出。

e = -4; m = 1.1001100110011001100110011001100110011001100110011010 (52位)

+

e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)


e = -3; m = 0.1100110011001100110011001100110011001100110011001101 (52位)

+

e = -3; m = 1.1001100110011001100110011001100110011001100110011010 (52位)


e = -3; m = 10.0110011001100110011001100110011001100110011001100111 (52位)


e = -2; m = 1.00110011001100110011001100110011001100110011001100111 (53位)

我们看到已经溢出来了(超过了52位),那么这个时候我们就要做四舍五入了,那怎么舍入才能与原来的数最接近呢?比如1.101要保留2位小数,那么结果有可能是 1.10 和 1.11 ,这个时候两个都是一样近,我们取哪一个呢?规则是保留偶数的那一个,在这里就是保留 1.10。

回到我们之前的就是取m=1.0011001100110011001100110011001100110011001100110100 (52位)

然后我们得到最终的二进制数

1.0011001100110011001100110011001100110011001100110100 * 2 ^ -2

=0.010011001100110011001100110011001100110011001100110100

现在转化为十进制,二进制小数转化为十进制的方法是小数点后第一位 *2 ^  -1第二位 *2 ^ -2,以此类推,最终我们用等比数列的求和公式得到十进制数为 0.30000000000000004,所以0.1 + 0.2 的最终结果是

0.30000000000000004

这就是整个推理过程,希望你也能试着推理一下,虽说这属于JavaScript里面比较偏的知识,但对于了解计算机的存储机制还是有好处的。


Twittytop
2 声望2 粉丝