带小数的数字进行加减乘除运算时可能会出现异常多位小数的情况,此时用封装好的方法进行运算可以避免这种问题。
问题描述
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 != 0.3
原因总结
- 0.1和0.2在转换为二进制时会产生无限循环小数,而计算机无法精确标志无限循环小数,所以会产生精度误差(整数转为二进制的方式是除二取余,小数转换为二进制的方式是乘二取整)
原因分析
- 浮点数值的最高精度是17位小数,但在进行运算的时候其精确度却远远不如整数;整数在进行运算的时候都会转成10进制;
- 而Java和JavaScript中计算小数运算时,都会先将两个十进制的小数换算为对应的二进制,一部分小数并不能完整的换算为二进制,这里就出现了第一次的误差。
- 待小数都换算为二进制后,再进行二进制间的运算,得到二进制结果。然后再将二进制结果换算为十进制,这里通常会出现第二次的误差。
- 注意:不以5结尾的小数从十进制转为二进制过程中精度都会丢失!
- 能被转化为有限二进制小数的十进制小数的最后一位必然以 5 结尾(因为只有
0.5 * 2
才能变为整数)。(可参考十进制转二进制中小数部分的转换帮助理解) - 所以十进制中一位小数
0.1 ~ 0.9
当中除了0.5
之外的值在转化成二进制的过程中都丢失了精度,因为无限二进制小数 + 浮点数值的最高精度是17位小数 = 精度丢失
- 能被转化为有限二进制小数的十进制小数的最后一位必然以 5 结尾(因为只有
例子
- 重点前提条件:不以5结尾的小数从十进制转为二进制过程中精度都会丢失!
- 以0.1和0.2的加法举例:
- 小数部分十进制转为二进制:
0.1
转为二进制是0.0001100110011001100110011001100110011001100110011001101...
,有限十进制小数0.1
却转化成了无限二进制小数0.00011001100...
,且浮点数值的最高精度是17位小数,所以精度在转化过程中丢失了!0.2
转的二进制0.001100110011001100110011001100110011001100110011001101...
也是如此
- 二进制之间相加后转为十进制: 二进制加法计算以后再转十进制,自然而然就带了一串小数了(毕竟精度不丢失才是正常位数,前面就丢了精度了,相加之后自然距离正常结果有一部分差距)
- 小数部分十进制转为二进制:
- 所以0.1 + 0.2 != 0.3
解决方法
- 方法1:转换为整数=>相加=>转换回小数
- (0.1 * 10 + 0.2 * 10) / 10
- 方法2:使用number对象的toFixed()将运算结果四舍五入为指定小数位数的数字
- (0.1 + 0.2).toFixed(1)
- 方法3:使用封装好的计算方法
参考
扩展:js中如何比较两个浮点数是否相等
- 使用Number.EPSILON属性
1
2
3
4
5function isEqual(num1, num2) {
return Math.abs(num1 - num2) < Number.EPSILON
}
console.log(isEqual(0.1 + 0.2, 0.3) // true - Math.abs(num1 - num2):两个数的差值的绝对值
- Number.EPSILON:js中能表示的最小精度
- 如果两个数的差值的绝对值小于js中能表示的最小精度,则认为这两个数相等,返回true,否则false