一、场景

0.1 + 0.2 = 0.30000000000000004 

1.5 - 1.2 = 0.30000000000000004

19.9 * 100 = 1989.9999999999998

0.3 / 0.1 = 2.9999999999999996

 

二、原因

js中的数字只有 Number这种类型,其存采用的64位双精度浮点数(1位符号位、11位指数位,52位小数位),如下:

javascript 整数 精度 js数字精度问题_数位

做运算操作时会将10进制小数转换为2进制小数,整数部分采用除2取余法,如下:

 

javascript 整数 精度 js数字精度问题_Math_02

 

 

小数部分采用的“乘2取整,顺序排位法”,以0.1为例,如下:

 

javascript 整数 精度 js数字精度问题_Math_03

 

问题是不可能乘以2后恰好为整数,此时就会导致无限循环,而存储结构中的尾数部分最多只能表示 52 位,超出的会被舍弃掉,所以机器中存储的就是一个近似值,这就是导致小数精度的问题所在

三、解决办法 

思路1:缩放法

整数不会有精度问题,因此先将小数放大为整数进行运算,运算完再将结果缩为小数。

1.先找出小数位数最多的数字作为x(例:a为0.1,b为0.22,那么x就是2),然后每个数字都乘以10的x次方变为为整数,方法如下

Number(`${n}e${x}`)  //n为需要变换的数字,e在计算机中表示底数为10,x为小数位数。 转换也可以写成n*Math.pow(10,x)

计算完成后,加减法用结果除以10的x次方,将结果变为小数。

result / Math.pow(10,x)

乘法:

result/Math.pow(10,x*2) //如果是3个数,则将2换为3,以此类推

除法

假设a和b同时都放大了10的x次方,那么商相当于没有放大(也就是放大了10^0 = 1),由于商可能为小数,因此可以利用除以一个数,等于乘以该数的导数这个公式,调用乘法再次进行转换。

/**
 * 计算小数位数
*/
function digitLength(num) {
   const arr = num.toString().split('.');
   return arr[1]!==undefined ? arr[1].length : 0;
}
/**
 * 乘法
 */
 function times(a,b) {
    const x = digitLength(a),y = digitLength(b);
    const max = Math.max(x,y);
    const int_a = Number(`${a}e${max}`);
    const int_b = Number(`${b}e${max}`);
    const result = int_a * int_b;
    return result / Math.pow(10,x*2);
}
/**
 * 除法
 */
 function divide(a,b) {
    const x = digitLength(a),y = digitLength(b);
    const max = Math.max(x,y);
    const int_a = Number(`${a}e${max}`);
    const int_b = Number(`${b}e${max}`);
    const result = int_a / int_b;
    return times(result, 1); //result可能为小数,因此使用乘法,1的倒数还是1
}

  

思路2:

使用toFixed()降低小数精度