小数计算出现异常多位小数的问题

带小数的数字进行加减乘除运算时可能会出现异常多位小数的情况,此时用封装好的方法进行运算可以避免这种问题。

问题描述

  • 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结尾的小数从十进制转为二进制过程中精度都会丢失!
  • 0.1和0.2的加法举例
    1. 小数部分十进制转为二进制
      • 0.1转为二进制0.0001100110011001100110011001100110011001100110011001101...有限十进制小数 0.1 却转化成了无限二进制小数 0.00011001100...,且浮点数值的最高精度是17位小数,所以精度在转化过程中丢失了!
      • 0.2转的二进制0.001100110011001100110011001100110011001100110011001101...也是如此
    2. 二进制之间相加后转为十进制: 二进制加法计算以后再转十进制,自然而然就带了一串小数了(毕竟精度不丢失才是正常位数,前面就丢了精度了,相加之后自然距离正常结果有一部分差距)
  • 所以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
    5
    function 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
,