一、场景
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()降低小数精度