关于计算机中浮点数的精度计算问题

一、问题

  • 在计算机中我们可能偶尔会遇到一些小数的计算,得到的结果并不是我们想要的结果,如:
    console.log(0.1 + 0.2);     // 0.30000000000000004
    console.log(0.1 + 0.7);		// 0.7999999999999999
    console.log(0.1 * 3);		// 0.30000000000000004
    
  • 以上代码中,0.1加0.2,结果应该是0.3,但这里却多出了0.00000000000000004。
  • 这是因为计算机中对于浮点数的计算方式而导致的问题。

二、原理

  • 我们知道计算机的底层原理是电路的开关,转成数据就是二进制0和1
  • 计算机中所有的数值运算,都要转成二进制后才能进行运算,而浮点数转换成二进制小数会出现一些小问题
  1. 将十进制的整数转化为二进制整数时,将要转换的整数连续除以2,直到数字为1,作为二进制首位,再将余数从下向上连接,就可以得到二进制数。如:
    36/2 = 18 ---- 0 (余数)
    18/2 = 9  ---- 0 (余数)
    9/2  = 4  ---- 1 (余数)
    4/2  = 2  ---- 0 (余数)
    2/2  = 1  ---- 0 (余数)
    1
    36转成二进制为100100
    
    53/2 = 26 ---- 1
    26/2 = 13 ---- 0
    13/2 = 6  ---- 1
    6/2  = 3  ---- 0
    3/2  = 1  ---- 1
    1
    53转成二进制为110101
    
  2. 将十进制的小数转化为二进制时,将小数部分不断乘以2,直到小数部分全为0,再将每次相乘结果的整数位从上向下连接。就可以得到二进制的小数。如:
    0.125 * 2 = 0.25 ---- 0 (取0.25的整数部分)
    0.25 * 2  = 0.5  ---- 0 (取0.5的整数部分)
    0.5 * 2   = 1.0  ---- 1 (取1.0的整数部分)
    0.125转化为二进制为0.001
    
    然而并不是所有的十进制小数都能转化为二进制小数
    
    如十进制的0.1:
    0.1 * 2 = 0.2 ---- 0
    0.2 * 2 = 0.4 ---- 0
    0.4 * 2 = 0.8 ---- 0
    0.8 * 2 = 1.6 ---- 1
    0.6 * 2 = 1.2 ---- 1
    0.2 * 2 = 0.4 ---- 0
    0.4 * 2 = 0.8 ---- 0
    0.8 * 2 = 1.6 ---- 1
    将十进制的0.1转化为二进制是0.00011001100110011...是个无限循环的二进制小数
    
  • 而计算机对于数据的存储位数有限的,当出现无限小数时,计算机会将超出最大位数部分进行取最近值处理(二进制状态)
  • 所以当我们进行浮点数计算时,计算机真正计算的数据已经不是我们本来的数据了,在计算结束后,将二进制再转回十进制,必然会出现精度问题,而得不到理想的数据。

三、解决

  1. 可以选择性忽略一些精度要求不高的运算(不可取)

  2. 可以先将十进制的小数转化为整数再进行运算,因为计算机对整数的运算的是准确的

  3. 在已知理想结果的小数位的情况下,使用 x.toFixed(n) 主动四舍五入保留n位小数,达到理想值

    var num = (0.1 * 0.3).toFixed(2) - 0;
    console.log(num);		// 0.3
    
  4. 直接使用函数 x.toPrecision(1) ,指定精度位

    var num = (0.1 * 0.3).toPrecision(1) - 0;
    console.log(num);		// 0.03
    

以上,不足之处,欢迎留言指出

拓展阅读:js浮点数精度问题的前世今生?

猜你喜欢

转载自blog.csdn.net/weixin_41636483/article/details/123059403