或许很多人都遇到过,js 对小数的加、减、乘、除时经常得到一些奇怪的结果!
比如 :0.1 + 0.2 = 0.3 ?
这么一个简单的计算,当你用js 计算时会发现结果是:0.30000000000000004 。这么奇葩,简直无法理解!
那,为什么会这样呢?
对于浮点数的四则运算,几乎所有的编程语言都会有类似精度误差的问题,只是 C++ / C# / Java 这些语言中已经封装好了方法来避免精度的问题,而javascript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。
那为什么 0.1 + 0.2 这种简单的运算,计算机还搞不定呢?那是因为,计算机无论做什么运算,其实都是转成二进制来运算的,因为它只能读懂二进制,而不是十进制,所以我们先把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。
找到原因了,那应该怎么处理呢?
方法一:指定要保留的小数位数(0.1+0.2).toFixed(1) = 0.3;这个方法toFixed是进行四舍五入的也不是很精准,对于计算金额这种严谨的问题,不推荐使用,而且不通浏览器对toFixed的计算结果也存在差异。
方法二:把需要计算的数字升级(乘以10的n次幂)成计算机能够精确识别的整数,等计算完毕再降级(除以10的n次幂),这是大部分编程语言处理精度差异的通用方法。
eg: (0.1*10 + 0.2*10) / 10 == 0.3
对于方法二可以对四则运算分别作封装:
/* ===== 浮点型数据的加、减、乘、除 ===== */ add(arg1, arg2) { // 加法 let r1, r2, m 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)) return (arg1 * m + arg2 * m) / m }, sub(arg1, arg2) { // 减法 let 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) }, mul(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) }, div(arg1, arg2) { // 除法 let t1 = 0 let t2 = 0 let r1 let 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('.', '')) return (r1 / r2) * Math.pow(10, t2 - t1) }, /* ===== 浮点型数据的加、减、乘、除 ===== */