大多数语言在处理浮点数的时候都会遇到精度问题,但是在JS里似乎特别严重,比如:
console.log(1.1-0.5);
结果居然是0.6000000000000001,加减乘除之类的都会有这个问题。
那这是js的错误吗?
当然不是,你的电脑做着正确的二进制浮点运算,但问题是你输入的是十进制的数,电脑以二进制运算,这两者并不是总是转化那么好的,有时候会得到正确的结果,但有时候就不那么幸运了。
alert(0.7+0.1);//输出0.7999999999999999
alert(0.6+0.2);//输出0.8
你输入两个十进制数,转化为二进制运算过后再转化回来,在转化过程中就会有损失。
但一般的损失往往在乘除运算中比较多,而JS在简单的加减法里也会出现这类问题,这个误差虽然非常小,但却是不该出现的。
有一种比较简单的逻辑,比如0.7+0.1,先把0.1和0.7都乘10,加完之后再除10。
按照这个逻辑,我们可以对加减乘除进行封装如下:
(function () {
var calc = {
/*
函数,加法函数,用来得到精确的加法结果
说明:函数返回较为精确的加法结果
参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Add(arg1,arg2,d)
返回值:两数相加的结果,返回类型为string
*/
Add: function (arg1, arg2, d) {
// 将传入的arg1和arg2转为字符串再进行切割,获取小数点的位数
let arg1Arr = arg1.toString().split("."), arg2Arr = arg2.toString().split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""
d = typeof d === "number" ? parseInt(d) : Math.max(d1.length, d2.length) // 保留的小数位数等于 传入的d 或者 两个数的小数点后位数
let m = Math.pow(10, d) // 取10的maxLen次方
return Number(((arg1 * m + arg2 * m) / m).toFixed(d)) // 判断传入的d是否为number类型,是的话进行截取小数点后d位,不传就是undefined。
},
/*
函数:减法函数,用来得到精确的减法结果
说明:函数返回较为精确的减法结果
参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Sub(arg1,arg2,d)
返回值:两数相减的结果
*/
Sub: function (arg1, arg2, d) {
return Calc.Add(arg1, -Number(arg2), d)
},
/*
函数:乘法函数,用来得到精确的乘法结果
说明:函数返回较为精确的乘法结果。
参数:arg1:第一个乘数;arg2第二个乘数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Mul(arg1,arg2,d)
返回值:两数相乘的结果
*/
Mul: function (arg1, arg2, d) {
d = typeof d === "number" ? parseInt(d) : (arg1.toString().split(".")[1] ? arg1.toString().split(".")[1].length : 0) + (arg2.toString().split(".")[1] ? arg2.toString().split(".")[1].length : 0)
return Number(arg1.toString().replace(".", "")) * Number(arg2.toString().replace(".", "")) / Math.pow(10, d)
},
/*
函数:除法函数,用来得到精确的除法结果
说明:函数返回较为精确的除法结果。
参数:arg1:除数;arg2被除数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数)
调用:Calc.Div(arg1,arg2,d)
返回值:arg1除于arg2的结果
*/
Div: function (arg1, arg2, d) {
d = typeof d === "number" ? parseInt(d) : (arg2.toString().split(".")[1] ? arg2.toString().split(".")[1].length : 0) - (arg1.toString().split(".")[1] ? arg1.toString().split(".")[1].length : 0)
return Number(arg1.toString().replace(".", "")) / Number(arg2.toString().replace(".", "")) * Math.pow(10, d)
}
};
window.Calc = calc;
}());
该方法返回值是string类型,是因为:
toFixed(num)方法返回 NumberObject 的字符串表示,不采用指数计数法,小数点后有固定的 num 位数字。如果必要,该数字会被舍入,也可以用 0 补足,以便它达到指定的长度。如果 num 大于 le+21,则需调用 NumberObject.toString(),返回采用指数计数法表示的字符串。
参考文章:
https://www.iteye.com/blog/talentluke-1767138
https://blog.csdn.net/baidu_29701003/article/details/88103436
https://blog.csdn.net/yexuan14/article/details/84561788