我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
前言
我们在使用 JavaScript 计算 0.1+0.2 会出现不等于0.3的情况,其实不只是 JavaScript 中会存在这个问题。只要是使用 IEEE 754 规范都会存在这个问题比如说 java 中也会存在这个问题。
IEEE 754
在 IEEE 754 规范中双精度浮点数使用 64 进行存储的。
对于64为双精度浮点数来说最高位为符号位 S 紧接着11位为指数位E最后剩余的52位为有效数字 M。
计算公式:
有效数字
在 IEEE 754 规定在计算机内部存储有效数字 M 时,默认这个的第一位为1,此时会被舍去只保存后面部分。比如说计算机在保存 110.001 会只保存10001。这也是为什么我们在看二进制需要先找到第一个 1 出现的位置。
指数
在 IEEE 754 规定在计算机内部存储指数 E 时,在双精度浮点数中E 11 位取值范围为 [0,2047] 。为了表示指数 E 的正负所以必须减去1023。其中 [0,1022]表示负数指数,[0,1024]表示正指数。比如说 110.001 的指数为 2 ,保存时 1023+2 即 10000000001。
-
当指数位中
11位都为1时,有效数字为0时表示无穷Infinity,结合符号位就有了正无穷Infinity与负无穷之分-Infinity。 -
当指数位中
11位都为1时,有效数字不为0时表示NAN。
解释一下为什么 0.1 + 0.2 !=== 0.3
首先我们先来看一下十进制如何转换成二进制的,对于一个浮点数我们需要对整数与小数部分分别求值,整数部分除以2取余;小数部分乘以2取整。
我们在此举个例子,将十进制6.125转换为二进制过程如何下:
此时我们得到 6.125 的二进制位 110.001 即 1.10001 * 2^2 。在计算机中存储为:
0 10000000001 110001
0.1 + 0.2的计算过程
了解完十进制转换成二进制的过程我们对 0.1 与 0.2分别转换成为二进制,在JavaScript中我们可以通过toString(2)来得到二进制。
(0.1).toString(2)
// 0.0001100110011001100110011001100110011001100110011001101
(0.2).toString(2)
// 0.001100110011001100110011001100110011001100110011001101
将其转换为科学计数法
0.1 = 1.1001100110011001100110011001100110011001100110011010 * 2^-4
0.2 = 1.1001100110011001100110011001100110011001100110011010 * 2^-3
次方数不同不能进行计算我们需要将0.1的转为-3次方,这边需要注意由于指数从-4转化为-3,此时有效位数已经超出52位由于超出部分为0直接舍去。
0.1 = 0.1100110011001100110011001100110011001100110011001101 * 2^-3
0.2 = 1.1001100110011001100110011001100110011001100110011010 * 2^-3
计算0.1+0.2会发生进位
10.110011001100110011001100110011001100110011001100111 * 2^-3
将-3次方转换为-2次方,这时有效数字位数大于52且被截取部分为1那么需要往前进1,转化得
1.011001100110011001100110011001100110011001100110100 * 2^-2
在计算机内存中表示
0 1111111101 1011001100110011001100110011001100110011001100110100
总结
最后我们可以总结出
-
我们在计算0.1与0.2的二进制时由于由于位数是无限循环的我们已经对齐进行截取并保留52为有效位数。
-
由于进行加法计算时将0.1的次方数从-4变成-3过程中也存在尾数截取但是0不存在向前进1。
-
计算结果从-3次方变为-2次方还是存在尾数截取此时截取为是1我们需要向前进一。
解决方案
在我们实际工作中可以使用toPrecision方法来解决0.1 + 0.2 !=== 0.3。
(0.1+0.2).toPrecision(12)
// 0.300000000000
Number((0.1+0.2).toPrecision(12)) === 0.3
// true