为什么JavaScript中 0.1 + 0.2 不等于0.3?

192 阅读3分钟

问题描述

相信很多接触过 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
}