js小数精度问题

一、场景

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位小数位),如下:
做运算操作时会将10进制小数转换为2进制小数,整数部分采用除2取余法,如下:
  
 
小数部分采用的“乘2取整,顺序排位法”,以0.1为例,如下:
  
问题是不可能乘以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()降低小数精度

 

其他:

使用mathjs函数库

posted @ 2022-03-17 21:17  我是格鲁特  阅读(2131)  评论(0编辑  收藏  举报