JavaScript 精度问题的原因和解决方案

前置知识点

数值精度丢失问题

Javascript 是使用二进制进行计算的,并且有长度限制。另外对于整数和小数转换成二进制的形式是不一样的。
整数转换成二进制使用的是除二取余,小数转成二进制使用的是将小数乘以二取整

我们通过 toPrecision 方法指定精度来查看我们写的数值对象的字符串表示

1
2
3
Number(9).toPrecision(30) // '9.00000000000000000000000000000'
Number(9.55).toPrecision(30) // '9.55000000000000071054273576010'
Number(9.555).toPrecision(30) // '9.55499999999999971578290569596'

可以看到我们定义的小数,在用到这个值时实际取出的值是有偏差的,对此还有一个经典的问题:0.1 + 0.2 !== 0.3

1
2
3
4
Number(0.1).toPrecision(30) // '0.100000000000000005551115123126'
Number(0.2).toPrecision(30) // '0.200000000000000011102230246252'
// 以上两个值相加
0.100000000000000005551115123126 + 0.200000000000000011102230246252 // 0.30000000000000004

可以看到 0.1 和 0.2 都有误差,计算时就不准确了,那其他小数呢?

1
2
3
4
Number(0.4).toPrecision(30) // '0.400000000000000022204460492503'
Number(0.5).toPrecision(30) // '0.500000000000000000000000000000'
// 以上两个值相加
0.400000000000000022204460492503 + 0.500000000000000000000000000000 // 0.9

可以看到 0.4 其实也不准,但他和 0.5 相加却可以准确的得出 0.9,这是因为 Javascript 数字在计算时,小数最大保存 17 位,整数最大保存 15 位,所以在计算时精度差在这个范围内是可以计算正确的。

js的科学计数法

在 JavaScript 中使用 number 类型的变量时,当小数超过6位时,整数超过 21 位 js会自动转为科学计数法

1
2
console.log(0.0000001) // 1e-7
console.log(5676543211234567899876) // 5.676543211234568e+21

Lodash 的 round 是如何处理精度问题的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createRound(methodName) {
const func = Math[methodName]
return (number, precision) => {
precision = precision == null ? 0 : (precision >= 0 ? Math.min(precision, 292) : Math.max(precision, -292))
// 如果精度不为0
if (precision) {
// pair 是将 number 转为科学计数法形式数组.第一位是 数值,第二位是指数
let pair = `${number}e`.split('e')
// 将 number 扩大/缩小 精度倍数,然后通过 Math.round 对整数进行四舍五入
const value = func(`${pair[0]}e${+pair[1] + precision}`)
pair = `${value}e`.split('e')
// 缩小/扩大 精度倍数
return +`${pair[0]}e${+pair[1] - precision}`
}
// 如果精度为 0 直接使用 Math.round 进行舍入
return func(number)
}
}

const round = createRound('round');

其实实现方式很简单,就是将数字变成整数,使用 Math.round 进行舍入,最后变回原值即可。


JavaScript 精度问题的原因和解决方案
https://l1ushun.github.io/2023/03/11/lodash-round/
作者
liu shun
发布于
2023年3月11日