一、出现浮点数失精的原因
小数失精度问题的原因是数字计算时,要先转成二进制再进行计算,计算完后再转成IEEE 754 浮点数。
有的小数再转二进制时会出现无限循环的情况,最后计算完再转回来就会导致精度丢失。
例:0.1转二进制的过程,整数部分为二进制数据,出现了死循环。
小数 | x2的结果 | 整数部分 |
---|---|---|
0.1 | 0.2 | 0 |
0.2 | 0.4 | 0 |
0.4 | 0.8 | 0 |
0.8 | 1.6 | 1 |
0.6 | 1.2 | 1 |
0.2 | 0.4 | 0 |
… | … | … |
二、常规加减乘除方法
- 将小数化成整数后再作运算
- 避免方法中出现小数相乘
1、加
- 转成整数相加
Math.pow(10, ... )
为整数,与和相除不会丢失精度
/**
* 加法运算,避免数据相加小数点后产生多位数和计算精度损失。
* @param num1 加数1
* @param num2 加数2
*/
function numAdd(num1, num2) {
var baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.toString().split(".")[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split(".")[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
return (num1 * baseNum + num2 * baseNum) / baseNum;
};
2、减
- 减数和被减数,小数最多位作为精度值
/**
* 减法运算,避免数据相减小数点后产生多位数和计算精度损失。
* @param num1 被减数
* @param num2 减数
*/
function numSub(num1, num2) {
var baseNum, baseNum1, baseNum2;
var precision;// 精度
try {
baseNum1 = num1.toString().split(".")[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split(".")[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
precision = (baseNum1 >= baseNum2) ? baseNum1 : baseNum2;
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
};
3、乘
- 转成整数相乘
Math.pow(10, ... )
为整数, 与乘积相除不会丢失精度
/**
* 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。
* @param num1 被乘数
* @param num2 乘数
*/
function numMulti(num1, num2) {
var baseNum = 0;
try {
baseNum += num1.toString().split(".")[1].length;
} catch (e) {
}
try {
baseNum += num2.toString().split(".")[1].length;
} catch (e) {
}
return Number(num1.toString().replace(".", "")) * Number(num2.toString().replace(".", "")) / Math.pow(10, baseNum);
};
4、除
- 转成整数相除
- 必须使
Math.pow(10, ... )
结果为整数 num1
小数位大于num2
, 商乘以Math.pow(10, baseNum2 - baseNum1)
不会丢失精度num1
小数位小于num2
, 商除以Math.pow(10, baseNum2 - baseNum1)
不会丢失精度
/**
* 除法运算,避免数据相除小数点后产生多位数和计算精度损失。
* @param num1 被除数
* @param num2 除数
*/
function numDiv(num1, num2) {
var baseNum1 = 0, baseNum2 = 0;
var baseNum3, baseNum4;
try {
baseNum1 = num1.toString().split(".")[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split(".")[1].length;
} catch (e) {
baseNum2 = 0;
}
/*
with (Math) {
baseNum3 = Number(num1.toString().replace(".", ""));
baseNum4 = Number(num2.toString().replace(".", ""));
return (baseNum3 / baseNum4) * pow(10, baseNum2 - baseNum1);
}
*/
baseNum3 = Number(num1.toString().replace(".", ""));
baseNum4 = Number(num2.toString().replace(".", ""));
if( (baseNum2 - baseNum1) < 0){
return (baseNum3 / baseNum4) / Math.pow(10, baseNum1 - baseNum2)
}
return (baseNum3 / baseNum4) * Math.pow(10, baseNum2 - baseNum1);
};
三、精度的另一种解决方案
parseFloat((0.1 + 0.2).toFixed(10))
原理:小数部分最多有17位,当出现失精的情况下,往往中间都是0
,而最后一位是数字
截取小数点后十位,得到这样的结果:
最后转成浮点数,即想要的结果