问题描述
相信很多接触过 JavaScript 的童鞋,都遇到过这样的问题,甚至我本人在面试的时候也遇到过这道题,就直勾勾地问,0.1 + 0.2 = ? 我当时差点就蒙了。
相信大家都知道这个结果,但是到底是为什么?是不是只有 0.1 + 0.2 会出现这种奇怪的现象?还是说只有加法运算中才会出现?其他运算会不会也有这样的结果?今天我就想和大家聊一聊为什么会出现这种现象。
显而易见,并不是只有加法的时候才会出现这种现象,比如:
相信大家都知道,当我们输入一个值的时候,计算机肯定不是直接就把我们的值存储进去的,你可能听说计算机是用一大串0和1组成的二进制来存储十进制数字的。一大串0和1 ?那这一大串到底有多大?
原因分析
你可能听说过 IEEE754 ,也可能没听说过,IEEE754 是浮点数的运算标准(看这名字就很像)。IEEE754 分为:单精度、双精度(64位)、延伸单精度、延伸双精度。而 JavaScript 就是采用了双精度(64位)来处理浮点数的。简单的看一下什么是双精度(64位):
-
64位双精度:一个数字会用固定的64位0和1来存储;
-
计算机把这64位分为3部分:
-
符号位:只有0和1,用来区分正数与负数。0: 正数 ,1: 负数
-
指数位:储存数字的指数,如 2^4,指数位就是4 。11位
-
有效位:储存数字的底数,如1.11 ^ 4,则有效位储存的就是1.11。52位
我就在想,这些整数都能完整的存储到计算机里头,那 0.1 自然也是没有问题的,何况0.1又不是无理数或者无限循环小数。如果你也是这么想,那就想到点子上去了,只有64位数,那无限循环小数和无理数,又该怎么表示呢?
如果知道二进制的换算方法的小伙伴可以算一下(这里就不做计算过程的展示了,感觉不知道也没关系)。
0.1 用二进制怎么表示,实际上0.1 转成二进制是 0.00 0110 0110 ... 0110 无限循环 0110。而0.2 转换后也是一个无限循环的数。但是用 IEEE754 标准来存储的话,肯定是没法把这个无限长的数字存储进去的,因为IEEE754 的指数只有,有效位只有52位。这就迫使计算机取一个近似的数字。所以就出现了精度丢失问题。
解决方案
安装方式:npm install --save decimal.js
github地址:github.com/MikeMcl/dec…
使用方式:
- 加法:
new Decimal(a).add(new Decimal(b)).toNumber()) - 减法:new Decimal(a).sub(new Decimal(b)).toNumber()
- 乘法:new Decimal(a).mul(new Decimal(b)).toNumber())
- 除法:new Decimal(a).div(new Decimal(b)).toNumber())
更多计算方式可以查阅官方文档。
我们公司之前让我们部门开发一个计算年终奖,计算个人所得税的小程序,就会出现很多这样的数据处理。我在这个坑里可是爬起来又掉下去。如果你们有更好的解决方案,请留言告诉我。在此先谢谢各位了!
注意事项
关于数字,跟大家在分享一个小知识,NaN 不等于任何一个数字。如 NaN !== 0 返回的结果是true。除此之外,你可能还会想到下面这样的问题
(0.2) > (0.3 - 0.1)
从代码上来看,这是完全没有问题的,但是你或许会觉得我怎么可能写出这种代码。那要是我换成下面这种方式,或许问题就会变得明显许多
let a = 0.1,
b = 0.2,
c = 0.3
if (a > (c - b)) {
...do something
}