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));
四、扩展知识
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