在电商网站中经常有金额的计算,但是在js中加减乘除的计算并不准确,比如:0.1+0.2 = 0.3000000000000004。那么势必会造成线上的事故,毕竟关于钱的事都是大事。
我们可以通过引入 mathjs 解决这个问题
安装:npm install mathjs
引入和使用:
import * as math from 'mathjs';
let num = math.number(math.chain(math.bignumber(0.1))
.add(math.chain(math.bignumber(0.2))
.done());
// 0.3
常用方法:
方法名 | 方法 |
---|---|
加 | add() |
减 | subtract() |
乘 | multiply() |
除 | divide() |
转变为数字类型 | number() |
转变为bigNumber | bignumber() |
链式调用 | chain() |
具体使用方法可以直接查看文档:https://mathjs.org/docs/index.html
解决了计算精度问题之后,计算得出的金额往往是多位小数,在实际业务中,我们需要把多位小数保留小数点后两位,且进行四舍五入。
在网上查阅了资料并进行了多次实践之后,总结出了正确的四舍五入方法。首先看一下网上经常使用的几种无效方案
这应该是网上最多的解决方案了,实际上toFixed也会有精度问题,比如:
1.025.toFixed(2)
我们想要的结果是 1.03 ,但实际上计算出了1.02 的结果。
经过查找资料,了解了toFixed的运行逻辑:
主要看图中的这句话,大体意思就是如果toFixed的入参小于10的21次方,那么就取一个整数n,让n*10^f - x 的精确值尽可能的趋近于0,如果存在两个这样的n,取较大的n。
显然,当n=102的时候,n*10^f - x 更趋向于0,所以计算的结果是1.02,而不是1.03
直接看代码:
function round(number, presision) {
const [int, decimals] = String(number).split('.');
let precisionDecimals = +decimals.slice(0, presision);
if (decimals[presision] > 4) precisionDecimals++;
return parseFloat(int + '.' + precisionDecimals)
}
可以看出以上方式是通过截取小数位的方式进行计算,最后进行拼接。
有两个问题:1.没有考虑小数第一位0的问题,比如1.025的小数位是025,计算的时候会当成25计算 。2.没有考虑进位问题,比如1.999向前进位为2.000
总之,这个方法咔掉。
此方案有两个问题:
通过上面的分析,知道Math.round()是可以解决问题的,只需要保证参数是正数,且计算正确。
那么引入mathjs进行计算,并判断数值正负即可
round(number) {
let bigNumber = math.number(
math.chain(math.bignumber(number))
.multiply(math.bignumber(100))
.done()
);
if(number<0){
return -(Math.round(-bigNumber) / 100);
}else{
return Math.round(bigNumber) / 100;
}
}
这个问题并不难,相反很简单,只需要自己测试查找资料半小时就能解决。但恰恰是这个简单的问题,网上的解决方案却错误百出,连最基本的测试都没有进行过。写这篇博客除了记录下解决问题的过程,也是想吐槽一下现在的网络环境。
最后,如果大家有更好的方案,或者文中有错误的地方,欢迎大家帮我指出来,一起进步。