js四舍五入和计算精度问题
业务背景
在电商网站中经常有金额的计算,但是在js中加减乘除的计算并不准确,比如:0.1+0.2 = 0.3000000000000004。那么势必会造成线上的事故,毕竟关于钱的事都是大事。
我们可以通过引入 mathjs 解决这个问题
计算精度问题(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(2)
这应该是网上最多的解决方案了,实际上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(n*100)/100
此方案有两个问题:
- 进行负数运算的时候,不是按照四舍五入的规则进行的。
比如:Math.round(-11.5),结果为 -11
解决:只需要转为正数,最后在变为负数即可 - n*100的计算问题
比如:Math.round(1.025*100)/100,期望是1.03,但实际结果为1.02
是因为1.025*100的计算结果并不是我们想到102.5,而是102.49999999999999
那么只要通过上面的精准计算,就可以使用Math.round()了
最终方案
通过上面的分析,知道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;
}
}
后记
这个问题并不难,相反很简单,只需要自己测试查找资料半小时就能解决。但恰恰是这个简单的问题,网上的解决方案却错误百出,连最基本的测试都没有进行过。写这篇博客除了记录下解决问题的过程,也是想吐槽一下现在的网络环境。
最后,如果大家有更好的方案,或者文中有错误的地方,欢迎大家帮我指出来,一起进步。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/37050.html