先看以下计算的输出:javascript
0.1 + 0.2
显然是0.3。可是在javascript中,结果是什么呢?java
0.30000000000000004
这是程序语言在数值计算中很容易出现的精度问题,以下图饿了么帐单页金额显示。 浏览器
先来看对Number类型数值二进制的表示,由3部分组成:安全
符号位 * 指数位 * 尾数位
因为js采用64位双精度浮点数编码,实际存储时为了节省空间,采用科学计数法表示,其二进制构成以下: 编码
符号位占1位,指数位占11位,尾数占52位。spa
0.1的二进制表示为:code
0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 ...
其科学计数法表示为:blog
1.1001... * 2^-4
其中指数位采用偏置码处理,-4即为:01111111011。简单介绍下(自行百度):ip
双精度采用的偏置码为1023, 好比指数位:01111111011,其值为1019, 1019 - 1023 = -4
因为尾数位仅为52位,所以须要截取前52位,而且如若第53位为1则进1,反之舍去,所以0.1的尾数位截取后为:开发
//10011001 10011001 1001100 110011001 10011001 10011001 10011001... //因为53位为1,进1,即为: 10011001 10011001 10011001 10011001 10011001 10011001 1010
能够看到0.1的值其实已经不许确了,较原值偏大。其对应的二进制存储表示以下:
有的童鞋可能注意到了,尾数位存储的是小数部分,这是由于规格化后的值通式为1.x,所以能够略去1,节省了一个bit位空间。
同理0.2的二进制以下:
0.001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001...
科学计数法处理后的二进制存储为:
至此,已经清楚了javascript对数值的存储方式。
进制转换网址参见:http://www.binaryconvert.com/。
0.3的二进制表示为:
0.010011001100110011001100110011001100110011001100110011...
使用科学计数法表示后存储为:
而计算机在处理0.1+0.2时(上面已经知道了其分别对应的二进制存储方式),须要经过对阶、尾数求和、规格化、舍入等操做(这里再也不赘述),最终获得:
0.010011001100110011001100110011001100110011001100110100 //转为10进制即为:0.30000000000000004
能够知道,计算机在进行浮点数加减运算时,包括对阶、规格化过程均可能产生精度偏差,核心仍是由于尾数位的位数有限,1进0舍导入的偏差。
1. 取固定精度
有的童鞋可能会采用toFixed()获取固定精度,以下
(0.1+0.2).toFixed(1) = 0.3;
对于精度要求不高的话,这种经过4舍5入获取固定精度的方式通常能够知足需求。
2. 先将小数转为整数再进行计算
0.1 + 0.2 //将二者都转化为整数的最小公倍数:RATE = 10 (0.1*RATE + 0.2*RATE)/RATE = 0.3
这是平常开发中最经常使用的方式,推荐。
若是清楚上面讲解的数值存储方式,那么能够知道js的安全整数范围为:
Math.pow(2, 53) - 1 // 可表示的安全整数范围: // Number.MIN_SAFE_INTEGER ~ Number.MAX_SAFE_INTEGER -9007199254740991 ~ 9007199254740991
超出这个范围的整数计算会出现精度丢失问题。
须要处理较大值的话,能够参考bignumber.js等;另外ES2020,加入了BigInt类型:
let number1 = BigInt(123); //方式1 let number2 = 123n; //方式2 number1 == number2; //true typeof number1; //"bigint"
谷歌浏览器已经支持了,能够尝试下~
获取更多干货分享,请【扫码关注】~