关于 js 中的精度丢失问题
前言
在 JavaScript 中,由于采用了 IEEE 754 标准的浮点数表示方法,可能会导致精度丢失问题。这主要是因为浮点数在内存中以二进制的形式存储,而某些十进制数无法精确地转换成二进制表示。当进行计算时,就会出现舍入误差。
例如以下经典示例:

结果不精确是因为 0.1 和 0.2 无法用二进制无法精确地表示出来,在进行运算时也会存在误差。
本文将以此为例,从二进制转化,存储到计算来分析造成这一结果的根本原因,文章篇幅不多,希望看完后可以给你带来一些收获......
如何转化?
首先我们要清楚整数和小数是如何转化为二进制的。
整数部分:除2取余 + 逆序排列,示例如下:
8 / 2 = 4 ...... 0
4 / 2 = 2 ...... 0
2 / 2 = 1 ...... 0
1 / 2 = 0 ...... 1
整数 8 转化为二进制的结果是:1000
小数部分:乘2取整 + 顺序排列,示例如下:
0.1 * 2 = 0.2 ...... 0
0.2 * 2 = 0.4 ...... 0
0.4 * 2 = 0.8 ...... 0
0.8 * 2 = 1.6 ...... 1
0.6 * 2 = 1.2 ...... 1
......
小数 0.1 转化为二进制的结果是:0.0001100110011...
如何存储?
js 中 Number 类型使用 IEEE 754 标准 64 位存储,为每个数值分配 64 位存储空间,以科学计数法的方式进行存储。形式如下:
1.xxxxx * 2 ^ n
64 位存储空间分为 3 个部分,包括 符号位、指数位 和 小数位,如下图所示:

符号位占 1 位,标记数值的正负,0
表示正数,1
表示负数。
指数位占 11 位,值为一个固定值 1023 (IEEE 754 标准) + 科学计数法中的指数值,再将其转为 11 位二进制。比如 0.1 = 0.00011001... = 1.1001... * 2 ^ (-4),指数位就为 1023 + (-4) = 1019,用 11 位二进制表示为 01111111011
。
小数位占 52 位,用来存放小数点后的数值,以 0.1 为例,由于小数位只能存储 52 位,又因为第 53 位为 1,所以截取需要往前进一位再保存,这里就造成了第一次的精度丢失。
这样一来,0.1 和 0.2 在内存中存储就如下图所示,你也可以通过 0.1.toString(2)
和 0.2.toString(2)
来查看输出结果:

如何计算?
最后就是将 64 位双精度浮点数相加,首先我们把偏移量还原对齐,再进行相加,如下图所示:

相加后发现小数部分有 53 位,由于小数位只能存储 52 位,因此需要再次进行截取,这里就造成了第二次的精度丢失。将这个结果转化为十进制就得到了一开始我们打印的结果。
这也就是为什么 0.1 + 0.2 不等于 0.3 的原因。
解决方法
要想得到精确的计算结果,我们可以使用一些第三方库来解决,例如:
总结
js 用二进制处理数据,用 IEEE 754 双精度浮点数标准存储 Number 类型。
精度丢失是由于 IEEE 754 标准存储位有限导致的。
过程中会有两次精度丢失,一次在存储,一次在相加。
转载自:https://juejin.cn/post/7248606482014928953