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 位,超出的会被舍弃掉,所以机器中存储的就是一个近似值,这就是导致小数精度的问题所在
 
三、解决办法
 
3.1:缩放法
 
整数不会有精度问题,因此先将小数放大为整数进行运算,运算完再将结果缩为小数。
 
加法和减法
先找出最长的小数位数x,假设:a为0.1,b为0.22,那么x就是2。
计算放大倍数为 factor = Math.pow(10,x)
对每个数字都乘以factor变为整数:
计算完成后,用结果除以factor还原
a1 = 0.1*factor,b1 = 0.22*factor;
return (a1 + b1) / factor

乘法:

两个乘数小数点之后位数相加得到x

计算放大倍数factor = Math.pow(10,x)

两个数字分别乘factor变为整数,然后做乘法运算

还原时需要除以factor的y次方(y为参与运算的数字个数,因为每个数字都放大了factor倍,所以需要除factor的y次方)

const x = a的小数位数+b的小数位数;
const factor = Math.pow(10,x);
const result = (a * factor)* (b * factor);
return result / Math.pow(factor,2) //这里参与运算的数字是a和b,因此y =2

除法

先找出最长的小数位数x,假设:a为0.1,b为0.22,那么x就是2。

计算放大倍数factor = Math.pow(10,x)

对每个数字都乘以factor变为整数,然后相除即可(因为每个数都放大了factor倍,相当于没有放大,因此不需要还原):

a1 = 0.1*factor,b1 = 0.22*factor;
return (a1 * factor) / (b1 * factor)

3.2:其他方法

如果小数精度位数要求不高,使用toFixed()保留指定位数即可

parseFloat((a+b).toFixed(2));

使用mathjs函数库

四、扩展知识

Number 类型能精确表示的整数范围是从 -(2^53 - 1)2^53 - 1,也就是:

Number.MAX_SAFE_INTEGER // 9007199254740991 最长16位
Number.MIN_SAFE_INTEGER // -9007199254740991
可能的问题:
1.后端的id超出number能表示的最大范围,解决办法是输出为字符串类型
2.超出这个范围的数字怎么做数学运算?
如果你需要表示更大的整数,可以使用 BigInt 类型,它支持任意大小的整数,是在es2019中引入的。
const bigNumber = BigInt("9007199254740991") + BigInt(1); // 比 Number.MAX_SAFE_INTEGER 大
console.log(bigNumber); // 9007199254740992n

创建 BigInt:可以通过以下方式创建:

const bigInt1 = 123456789123456789123456789n; // 使用 'n' 结尾的字面量
const bigInt2 = BigInt("123456789123456789123456789"); // 使用构造函数

基本操作:支持常见的数学运算,如加、减、乘、除、幂等,但不能与 Number 混用直接运算。

const sum = 100n + 200n; // OK
const mixed = 100n + 200; // TypeError

 

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