使用背景
JS进行加减乘除计算时,经常会遇到计算结果不准确的情况,如:
解决方法
1. toFixed()
该方法遵循四舍五入的原则。当四舍五入可以满足系统需要时,可使用;但如果对数字精度要求高,则不符合要求。
2. JavaScript 数字处理的第三方库:big.js(big.js文档跳转)
big.js和bignumber.js、decimal.js都是同一个作者写的,都是用于JS的数字处理,但简单的消除加减乘除的计算误差使用big.js即可满足;
包的大小:big.js < bignumber.js < decimal.js;
bignumber.js 和 decimal.js 存储值的进制比 big.js 更高,因此当操作大量数字时,前两者的速度会更快。
big.js的使用
a. 使用npm安装依赖:npm install big.js
b. 在使用时引入:import Big from 'big.js';
c. 加法
注:最后的结果需要使用toString()转成字符串才可以正常显示,此时使用typeof数据类型是string,要转换成数字则可以使用Number(),此时使用typeof数据类型是number
<script>
const x = new Big(0.1);// 0.1
const y = x.plus(0.2); // 0.1 + 0.2
const z = Big(0.7).plus(x).plus(y); // 0.7 + 0.1 + 0.3
console.log(0.1 + 0.2);// 0.30000000000000004
console.log(0.7 + 0.1 + 0.3);// 1.0999999999999999
console.log(y.toString());// '0.3'
console.log(typeof (y.toString()));// string
console.log(Number(y.toString()));// 0.3
console.log(typeof (Number(y.toString())));// number
console.log(z.toString());// '1.1'
</script>
d. 减法
<script>
const x = new Big(0.3);
const y = x.minus(0.1); // 0.3 - 0.1
const z = Big(0.7).minus(x).minus(y); // 0.7 - 0.3 - 0.2
console.log(0.3 - 0.1);// 0.19999999999999998
console.log(y.toString());// '0.2'
console.log(z.toString());// '0.2'
console.log(typeof (z.toString()));// string
console.log(Number(z.toString()));// 0.2
console.log(typeof (Number(z.toString())));//number
</script>
e. 乘法
注意:位数太多或计算式中有科学计数法表示的数字,则结果会自动换算成科学计数法,若不想显示科学计数法需要自行转换
<script>
const x = new Big(0.1);
const y = x.times(0.2); // 0.1 * 0.2
const m = Big('7e+500').times(x).times(y); // '7e+500' * 0.1 * 0.02
const z = Big(0.05).times(x).times(y); // 0.05 * 0.1 * 0.02
console.log(0.1 * 0.2);// 0.020000000000000004
console.log(y.toString());// '0.02'
console.log(m.toString());// '1e-16'
console.log(z.toString());// '0.0001'
console.log(typeof (z.toString()));// string
console.log(Number(z.toString()));// 0.0001
console.log(typeof (Number(z.toString())));// number
</script>
f. 除法
<script>
let x = new Big(0.3);
let y = x.div(0.2); // 0.3 / 0.2
let z = x.div(y).div(0.08); // 0.3 / 1.5 / 0.08
console.log(0.3 / 0.2);// 1.4999999999999998
console.log(y.toString());// '1.5'
console.log(z.toString());// '2.5'
console.log(typeof (z.toString()));// string
console.log(Number(z.toString()));// 2.5
console.log(typeof (Number(z.toString())));// number
</script>
g. big.js还可以进行比较大小、取绝对值、取模、四舍五入等运算,基本满足普通计算需求,详见big.js文档
补充:科学计数法转换成普通数字格式
<script>
getFullNum(num) {
// 处理非数字
if(isNaN(num)) { return num; };
// 处理不需要转换的数字
var str = '' + num;
if(!/e/i.test(str)) { return num; };
return (num).toFixed(18).replace(/\.?0+$/, '');
}
let num = 0.0000000000000003;
let result = this.getFullNum(num);//调用方法
console.log(0.0000000000000003);// 3e-16
console.log(result);// 0.0000000000000003
</script>
3. 数字升级
对符号两边要计算的数字先全部转化成整数(即升级,如乘以10的n次幂),再做对应的运算,对结果再进行降级(除以10的n次幂),得到最终的结果。
原理:整数的运算不会有误差
a. 加法和减法
<script>
// 消除js计算误差:add是加数(减数),reduce是被加数(被减数),s是加减符号,n是指数
formatNum(add, reduce, s, num) {
let m = Math.pow(10, num); // m是升级/降级的数
let res = s == '+' ? (add * m + reduce * m) / m : (add * m - reduce * m) / m;
return (res * m) / m;
}
console.log(this.formatNum(0.1, 0.2, '+', 10))
console.log(this.formatNum(0.3, 0.2, '-', 10))
</script>
b. 乘法
<script>
// arg1乘数, arg2被乘数
accMul(arg1, arg2) {
let m = 0
let s1 = arg1.toString();//乘数转成字符串
let s2 = arg2.toString();//被乘数转成字符串
try { m += s1.split('.')[1].length; } catch (e) { }//计算小数点后的位数,用于升/降级
try { m += s2.split('.')[1].length; } catch (e) { }//计算小数点后的位数,用于升/降级
return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m);//去掉小数点,相乘,然后降级相应的数量
},
console.log(this.accMul(0.2, 0.11))
</script>
c. 除法
<script>
accDiv(arg1,arg2) {
let t1 = 0;
let t2 = 0;
let r1,r2;
try{ t1 = arg1.toString().split('.')[1].length; }catch(e) {}//
try{ t2 = arg2.toString().split('.')[1].length; }catch(e) {}
r1 = Number(arg1.toString().replace('.',''));
r2 = Number(arg2.toString().replace('.',''));//去掉小数点
if (t2 - t1 > 0) {
return (r1 / r2) * Math.pow(10,t2 - t1);
}else{
return (r1 / r2) / Math.pow(10,t1 - t2);
}
}
console.log(this.accDiv(0.03, 0.2))
</script>