导入

在我们平时编码的过程中,你一定遇到过这样的问题:

const a = 0.1;
const b = 0.2;
console.log(a + b); // 0.30000000000000004
console.log(a + b === 0.3); // false

可能我们平时遇到这个问题的时候就忽略掉了,并没有深究为什么会这样。甚至可能部分人还一直认为 0.1 + 0.2 = 0.3。

那是什么原因造成这样的结果?为什么 0.1 + 0.2 ≠ 0.3 呢?

其实这正是我们今天要说到的浮点数运算精度丢失问题。

对于一般的业务系统,“浮点数精度丢失”可能不一定会造成什么重大问题,所以并没有起很多程序员的重视;
但是对于质量和健壮性要求较强的系统(比如,金融、某宝、拼夕夕这种的),若是没有处理好精度丢失问题带来的后果可能是灾难性的。

那下面我们就来深究一下:

分析

计算机存储浮点数采用IEEE-754 标准,分为单精度浮点数 float(32位)和双精度浮点数 double(64位)。

这里十进制 0.1 的二进制表示为:

0.00011001100110011001100110011001100110011…(0011无限循环下去)
由于有效位数最多只能是23位,可以取到:0.0001100110011001101(第24位为1,向前进1)
这里十进制 0.2 的二进制表示为:
0.00110011001100110011001100110011001100110…(0011无限循环下去)
由于有效位数最多只能是23位,可以取到:0.0011001100110011010(第24位为1,向前进1)
那么 0.1 + 0.2 二进制相加为:
0.0001100110011001101 + 0.0011001100110011010 = 0.01001100110011001100111

0.01001100110011001100111 转化为十进制为:0.3000011444091797,可以看出结果无限接近于 0.3,而不是直接等于 0.3。因为算的时候都是无限小数,并不是位数多了就会准确。

如何解决

那么如何做这种精度的计算呢?

具体解决方案,要根据使用的场景来决定。

常用的方法:

  1. 浮点数比较
  • 解决方案:比较两个浮点数是否相等,需要通过两者之差的绝对值是否小于某个阈值来判断。
  • 举例:
console.log(1/3 == (1 - 2/3)); // false
console.log(Math.abs(1/3 - (1 - 2/3)) < 0.0000001) ; // true
  1. 浮点数相加
  • 解决方案:将小数只要转成整数,计算完后在通过整数除法得到结果
  • 举例:

0.1 + 0.2 = ( 0.1 * 10 + 0.2 * 10 )/ 10 = 3 / 10 = 0.3

console.log((0.1 * 10 + 0.2 * 10) / 10 === 0.3) // true

当然,这个 0.3 也不是精确的 0.3,但会在显示过程进行精度转换,通过整数运算,避免了小数运算过程中的丢失精度问题。

  1. 转变数据类型

Java 有一个 BigaungDecimal 类专门解决着一个问题,对于 Js,也可以参考Java的思路,把浮点数转换为字符串,一个字符一个字符的计算最后在相加。