一、问题
- 在计算机中我们可能偶尔会遇到一些小数的计算,得到的结果并不是我们想要的结果,如:
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
- 计算机中所有的数值运算,都要转成二进制后才能进行运算,而浮点数转换成二进制小数会出现一些小问题
- 将十进制的整数转化为二进制整数时,将要转换的整数连续除以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,直到小数部分全为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...是个无限循环的二进制小数
- 而计算机对于数据的存储位数有限的,当出现无限小数时,计算机会将超出最大位数部分进行取最近值处理(二进制状态)
- 所以当我们进行浮点数计算时,计算机真正计算的数据已经不是我们本来的数据了,在计算结束后,将二进制再转回十进制,必然会出现精度问题,而得不到理想的数据。
三、解决
-
可以选择性忽略一些精度要求不高的运算(不可取)
-
可以先将十进制的小数转化为整数再进行运算,因为计算机对整数的运算的是准确的
-
在已知理想结果的小数位的情况下,使用
x.toFixed(n)
主动四舍五入保留n位小数,达到理想值var num = (0.1 * 0.3).toFixed(2) - 0; console.log(num); // 0.3
-
直接使用函数
x.toPrecision(1)
,指定精度位var num = (0.1 * 0.3).toPrecision(1) - 0; console.log(num); // 0.03
以上,不足之处,欢迎留言指出
拓展阅读:js浮点数精度问题的前世今生?