在平时编码过程中,我们经常会涉及到一些浮点数的计算问题,但是在使用过程中,我们会发现一些或许不那么容易理解的问题,比如:
0.1+0.2=0.30000000000000004
在解释为什么出现这种情况之前,我们需要知道计算机是如何对浮点数进行存储的。
- 浮点数的存储
众所周知,计算机只能处理二进制的数据,因此在进行存储之前,需要对十进制的浮点数进行二进制处理,浮点数二进制转换规则为:整数部分除2取余,小数部分乘2取整
又因为浮点数的存储相较于整数类型存储时多了一个小数点,我们可以通过移位的方式来标识小数点的位置,为了统一计算,需要保证移位后的整数部分都为1,在存储时只关心小数部分,那么最终移位后的展示形式应为 1.f * 2^n
假设有一个浮点数二进制化之后的结果为 10010.01101
那移位后就应该表示为1.001001101 * 2^4
同理
0.0000010101移位后的结果是 1.0101 * 2^-6
而浮点数的存储及运算规则遵循的是 IEEE754 规范。(这个规范具体可以参见链接,此处不赘述) IEEE754 规范又细分为
- 单精确度(32位)
- 双精确度(64位)
- 延伸单精确度(43比特以上,很少使用)
- 延伸双精确度(79比特以上,通常以80位实现)
JS中的浮点数正是其中的 双精确度(64位),因此JS中的一个浮点数可以用以下的第一个公式表示出来
而sign、exponent、fraction的具体取值可以通过第二个公式取得
其中(exponent-1023)中的1023是一个偏移常量,不必纠结,因为exponent的位数为11位,所以偏移常量取值为2^(11-1)-1
其中
- value——真实值
- sign——符号位(用于表示正负,0为正 1为负)
- exponent——指数偏移值(下方会详细讲)
- fraction——分数值
有了以上的理论知识,接下来我们看一下一个浮点数具体是如何存储到计算机中的。
-
将浮点数转换为二进制
-
将转换后的浮点数进行移位操作
-
将移位后的结果带入到上方第二个公式中,算出sign、exponent、fraction的值
1)exponent算出的值不足11位的在前面补0,补满11位
2)fraction不足52位的在后面补0,补满52位;如fraction超出52位,则遵循 0舍1入 的规则进行舍入
-
将算出的sign、exponent、fraction带入到第一个公式中,得到最后的value
至此,我们已经知道了浮点数在计算机中是如何进行存储的。不难看出在数据转换的第3步有 0舍1入 的操作,也就是说有一部分数字在存进计算机时就已经是一个近似值,那么我们用近似值去进行计算的时候,拿到的结果就会存在一定程度上的误差。这也就能解释为什么0.1 + 0.2 !== 0.3了。