【3分钟技能get】javascript浮点数精度处理问题

先看以下计算的输出: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"

谷歌浏览器已经支持了,能够尝试下~

获取更多干货分享,请【扫码关注】~
head.png

相关文章
相关标签/搜索