业务场景
业务场景中会有单价*数量 = 合计,以及税率的一些计算,后端对这类type:decimal的数据,均采用Number类型保存。调研的过程中,发现了一些规律,超出一定限度,JavaScript就会有精度丢失的问题。
| 类型 | 限度 |
|---|---|
| 整数区间 | -9007199254740991 至 9007199254740991 |
| 小于-1,大于1的小数 | 不超过17位(不含小数点) |
| 大于-1,小于1的小数 | 小数位不超过17位 |
Javascript为什么会精度丢失问题呢?
计算机底层只有0 和 1, 所有的运算最后实际上都是二进制运算。js浮点数运算,会先将十进制数转化为二进制数值,整数我们可以用除2取余的方式,小数我们则可以用乘2取整的方式。计算过程如下:
0.1转换为二进制:
- 0.1 * 2,值为0.2,小数部分0.2,整数部分0
- 0.2 * 2,值为0.4,小数部分0.4,整数部分0
- 0.4 * 2,值为0.8,小数部分0.8,整数部分0
- 0.8 * 2,值为1.6,小数部分0.6,整数部分1
- 0.6 * 2,值为1.2,小数部分0.2,整数部分1
- 0.2 * 2,值为0.4,小数部分0.4,整数部分0
- 从0.2开始循环
0.1会转换为如下循环的二进制
0.1:0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1100 ...
js对浮点数的处理,是参照一个标准【IEEE 754标准】
第一位是符号位; 中间11位代表的是指数位;最后的52位代表尾数位
所以最终我们只能得到二进制的近似值(按照IEEE 754标准保留 52位,按 0舍1入 来取值),这样就会造成精度的丢失。
总结,JS浮点数精度的缺失实际上是因为浮点数的小数部分无法用二进制很精准的转换出来,而以近似值来进行运算的话,肯定就存在精度的问题
解决方案
在调研解决方案的过程中,已经有一些成熟的库来处理js的精度丢失问题。
比如:decimal.js、big-number、big.js,关于这三者的区别,可以参考这个链接。最后在资源文件大小、社区活跃度、是否能解决业务场景问题等方面决定使用big.js库。【文档】
目前我们的业务场景里,仅需要加、减、乘、除、toFixed五个工具函数,我们对使用库提供的api做了一个简单封装,如下:
最后补充
目前是使用了big.js解决了计算精度丢失问题,但是在对这些函数的入参、返回值都应该为String类型,才能真的避免丢失问题。
参考链接