likes
comments
collection
share

关于 js 中的精度丢失问题

作者站长头像
站长
· 阅读数 70

前言

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

例如以下经典示例:

关于 js 中的精度丢失问题

结果不精确是因为 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 个部分,包括 符号位指数位小数位,如下图所示:

关于 js 中的精度丢失问题

符号位占 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) 来查看输出结果:

关于 js 中的精度丢失问题

如何计算?

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

关于 js 中的精度丢失问题

相加后发现小数部分有 53 位,由于小数位只能存储 52 位,因此需要再次进行截取,这里就造成了第二次的精度丢失。将这个结果转化为十进制就得到了一开始我们打印的结果。

这也就是为什么 0.1 + 0.2 不等于 0.3 的原因。

解决方法

要想得到精确的计算结果,我们可以使用一些第三方库来解决,例如:

  • bignumber.js:提供了超高精度的数字处理能力,可以解决精度丢失问题。官方文档
  • decimal.js:可以精确表示浮点数,解决精度丢失问题。官方文档

总结

  1. js 用二进制处理数据,用 IEEE 754 双精度浮点数标准存储 Number 类型。

  2. 精度丢失是由于 IEEE 754 标准存储位有限导致的。

  3. 过程中会有两次精度丢失,一次在存储,一次在相加。

转载自:https://juejin.cn/post/7248606482014928953
评论
请登录