前置知识点
数值精度丢失问题
Javascript 是使用二进制进行计算的,并且有长度限制。另外对于整数和小数转换成二进制的形式是不一样的。
整数转换成二进制使用的是除二取余,小数转成二进制使用的是将小数乘以二取整
我们通过 toPrecision 方法指定精度来查看我们写的数值对象的字符串表示
1 2 3
| Number(9).toPrecision(30) Number(9.55).toPrecision(30) Number(9.555).toPrecision(30)
|
可以看到我们定义的小数,在用到这个值时实际取出的值是有偏差的,对此还有一个经典的问题:0.1 + 0.2 !== 0.3
1 2 3 4
| Number(0.1).toPrecision(30) Number(0.2).toPrecision(30)
0.100000000000000005551115123126 + 0.200000000000000011102230246252
|
可以看到 0.1 和 0.2 都有误差,计算时就不准确了,那其他小数呢?
1 2 3 4
| Number(0.4).toPrecision(30) Number(0.5).toPrecision(30)
0.400000000000000022204460492503 + 0.500000000000000000000000000000
|
可以看到 0.4 其实也不准,但他和 0.5 相加却可以准确的得出 0.9,这是因为 Javascript 数字在计算时,小数最大保存 17 位,整数最大保存 15 位,所以在计算时精度差在这个范围内是可以计算正确的。
js的科学计数法
在 JavaScript 中使用 number 类型的变量时,当小数超过6位时,整数超过 21 位 js会自动转为科学计数法
1 2
| console.log(0.0000001) console.log(5676543211234567899876)
|
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)) if (precision) { let pair = `${number}e`.split('e') const value = func(`${pair[0]}e${+pair[1] + precision}`) pair = `${value}e`.split('e') return +`${pair[0]}e${+pair[1] - precision}` } return func(number) } }
const round = createRound('round');
|
其实实现方式很简单,就是将数字变成整数,使用 Math.round 进行舍入,最后变回原值即可。