小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
作为一个前端开发人员,相信很多人在使用js计算价格等操作的时候,总会遇到浮点数的问题,经典场景如下:
为什么会出现上述浮点数问题呢?
原因在于对于浮点数的四则运算,几乎所有的编程语言都会有类似精度误差的问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。
对于上述 0.1 + 0.2 !== 0.3的情况,我们来分析下计算过程:
我们都知道计算机只能读懂二进制,而不是十进制,计算机会先把 0.1 和 0.2 转换成二进制:
0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)
上面我们发现0.1和0.2转化为二进制之后,变成了一个无限循环的数字,这在现实生活中,无限循环我们可以理解,但计算机是不允许无限循环的,对于无限循环的小数,计算机会进行舍入处理。进行双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。
应该如何解决这个问题呢?
JS提供了一个 toFixed 方法,toFixed可以指定保留小数点的位数,我们尝试一下:
看上去还不错,但是toFixed 有个不足:按照四舍五入保留位数,对于金额计算这种严谨的计算来说,有失偏颇
要想数据更加精准,更加严谨,我们不得不想出其他办法:
把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法
常见操作方法如下:
1. 加法函数
function add(a, b) {
var c, d, e;
try {
c = a.toString().split(".")[1].length;
} catch (f) {
c = 0;
}
try {
d = b.toString().split(".")[1].length;
} catch (f) {
d = 0;
}
return e = Math.pow(10, Math.max(c, d)), (mul(a, e) + mul(b, e)) / e;
}
2. 减法函数,
用来得到精确的减法结果 {javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显}
SubFn (arg1, arg2) {
var r1, r2, m, n
try { r1 = arg1.toString().split('.')[1].length } catch (e) { r1 = 0 }
try { r2 = arg2.toString().split('.')[1].length } catch (e) { r2 = 0 }
m = Math.pow(10, Math.max(r1, r2))
n = (r1 >= r2) ? r1 : r2
return ((arg1 * m - arg2 * m) / m).toFixed(n)
},
3. 乘法函数
MulFn(a, b) {
var c = 0,
d = a.toString(),
e = b.toString();
try {
c += d.split('.')[1].length;
} catch (f) {
// 后续操作
}
try {
c += e.split('.')[1].length;
} catch (f) {
// 后续操作
}
return Number(d.replace('.', '')) * Number(e.replace('.', '')) / Math.pow(10, c);
}
4. 除法函数
function div(a, b) {
var c, d, e = 0,
f = 0;
try {
e = a.toString().split(".")[1].length;
} catch (g) {}
try {
f = b.toString().split(".")[1].length;
} catch (g) {}
return c = Number(a.toString().replace(".", "")), d = Number(b.toString().replace(".", "")), mul(c / d, Math.pow(10, f - e));
}