JS计算偏差小谈

平时在写js代码时会用到一些简单的计算,比方说系统中咱们数据库储存的金额是分,前端展现的是元,因此在用户输入元以后要转成分传给后台,这个公式小学一年级就学过了html

1.11*100 = 111
复制代码

通常来讲这个计算结果是没问题的,可是在js里面却有这样的尴尬前端

1.11*100 = 111.00000000000001
复制代码

结果不是咱们想要的111,相似的状况还有git

0.1+0.2 = 0.30000000000000004   //加法
0.27-0.11 = 0.16000000000000003 //减法
19.9*100 = 1989.9999999999998   //乘法
0.3/0.1 = 2.9999999999999996    //除法
复制代码

通常遇到这种问题,咱们都有成熟的解决方案解决github

用着用着就习惯了,一直没有搞清楚为何会有这样的偏差。这两天正好有空,看了一些博客终于搞清楚了。数据库

双精度浮点数

JS 的数据类型比较特别,和C、Java 等语言的的数据类型不同,无论是 int、double、float 在 JS 里面都是Number类型。
要搞清楚为何有这个偏差,就要先介绍一下双精度浮点(double)json

双精度浮点数(double)使用 64 位(8字节) 来储存一个浮点数。 它能够表示十进位制的15或16位有效数字,其能够表示的数字的绝对值范围大约是 [2.2310^(-308),1.7910^(308)]。bash

这其中的64位bit又能够分为下面的格式spa

  • sign bit(符号位):0表明正数,1表明负数
  • exponent(指数):中间的11位用来表示次方数
  • mantissa(尾数):最后的52位用来表示精确度

上面的格式能够转换成这个这个公式 3d

在十进制中,整数部分能够是0~9,在二进制中整数部分只能是0~1,因此能够看到上面公式对应的整数部分只能是1,这样就能够不用管整数部分直接保留后面的小数部分就能够了。指数 exponent(E) 是一个无符号整型 (unsigned int) ,那么问题就来了,咱们怎么保留小数呢?按照科学计数法,若是E小于0才能够表示成小数,由于E是11位的,最大能够表示为2047,因此取一个中间值1023(十六进制为ox3FF),0~1022表示为负,1023~2047表示为正,这样就解决了小数的表示问题。code

咱们来看看数字 1 是怎么储存的

用上面的公式表示就是:(-1)^0 * 2^(1024-1023) * 1.0 = 1,再看一下 0.5 的储存形式

(-1)^0 * 2^(1022-1023) * 1.0 = 0.5,搞清楚这个,咱们再看看上面提到的 1.11*100 = 111.00000000000001 这个问题。

将 1.11 转换成二进制是这样的1.0001110000101000111101011100001010001111010111000011...(11100001010001111010循环)(十进制小数转二进制方法),换成64位浮点来表示,S为0,E为1023,mantissa(M)为0001110000101000111101011100001010001111010111000011,由于位数只有52位,后面循环的部分就被舍弃了,转成64位浮点数是这样的

而后转成10进制的就变成了

1.11000000000000009769962616701
复制代码

因此这里出现了问题,偏差就有了,究其根本仍是精度的问题。

还有一个问题?

为何我直接输入 1.11 获得的结果是 1.11,而不是1.11000000000000009769962616701 呢?

这个仍是精度问题,64位浮点的尾数是52位,由于整数部分只能是1因此能够省略一位,比方说

11.101 * 2^1001 能够格式化为 1.1101 * 2^1010,尾数部分M直接储存1101便可;
0.0011101 * 2^-1001 能够格式化为 1.1101 * 2^-1100,尾数部分M储存1101便可。  
复制代码

因此他能够表示的最大长度是53,即2^53 = 9007199254740992,因此双精度浮点能表示的最大精度是 16 位的,JS 会调用 toPrecision(16) 来作运算

1.11.toPrecision(16) = 1.110000000000000 //自动取整以后就是1.11
复制代码

若是精度调整一下,结果就不同了:

1.11.toPrecision(17) = 1.1100000000000001
1.11.toPrecision(20) = 1.1100000000000000977
复制代码

到这里终于真相大白了!

相关文章
相关标签/搜索