Javascript toFixed方法精度丢失问题及解决方案
我们在开发时会遇到购物车结账的场景,在计算一个商品折扣价格时会精确小数位,有时候会出现价格有一分钱的差异,涉及钱的问题都是比较敏感的。一般精确小数位,我们使用 toFixed() 方法。
问题原因
toFixed() 方法使用定点数表示法来格式化一个数字,返回值为String类型。该方法的舍入法则并不是简单的四舍五入,下面是 ECMAScript 规范对该方法的定义。
关于ECMAScript 规范对toFixed方法的定义很复杂,但我们需要关注的主要是红框中的规则,一般我们需要保留精度的数值都在这个范围。
我们使用公式来测试下边一组数据:
1.0000005.toFixed(6)
// 1.000001 正确
1.00000005.toFixed(7)
// 1.0000000 错误根据上图红框的中的条件,x<10^21,1.0000005 和1.00000005都小于10^21,所以我们可以直接带入公式 n / 10^f - x 来测试。
我们先用x=1.0000005代入公式来看看情况:
// 设n1=1000000
var n1 = 1000000;
var x = 1.0000005;
var f = 6;
console.log((n1 / Math.pow(10, f) - x));
// -5.00000000069889e-7
// 设n2=1000001
var n2 = 1000001;
var x = 1.0000005;
var f = 6;
console.log((n2 / Math.pow(10, f) - x));
// 4.999999998478444e-7由结果可知,当n1=1000001时,得到的结果取最接近0的值,故:
console.log(1.0000005.toFixed(6));
// 1.000001 正确再来试试把x=1.00000005代入公式:
// 设n1=10000000
var n1 = 10000000;
var x = 1.00000005;
var f = 7;
console.log((n1 / Math.pow(10,f) - x));
// -4.9999999918171056e-8
// 设n2=10000001
var n2 = 10000001;
var x = 1.00000005;
var f = 7;
console.log((n2 / Math.pow(10,f) - x));
// 5.000000014021566e-8由结果可知,当n1=10000000时,得到的结果取最接近0的值,故:
console.log(1.00000005.toFixed(7));
// 1.0000000 错误上面的例子示范了如何通过规范中定义的公式计算出结果,也是toFixed方法会丢失精度的原因之一。
解决方案
利用“科学计数法”扩大10的n次不会出现精度丢失的特性,将操作数化为整数运算避免精度丢失。
function toFixed(number, precision = 2) {
number = Math.round(+number + 'e' + precision) / Math.pow(10, precision) + '';
let s = number.split('.');
if ((s[1] || '').length < precision) {
s[1] = s[1] || '';
s[1] += new Array(precision - s[1].length).fill('0').join('');
}
return s.join('.');
}测试结果:
结语
本篇文章主要梳理了toFixed方法的精度丢失问题及解决方案,希望能帮助到大家~