本文主要是意识流写法,想到哪写到哪, 估计有些废话, 自行过滤(逃...)。 前端开发应该都知道这是浮点数运算一个经典的问题了,网上关于这个问题的文章也一抓一大把,以前对这个问题也只是非常粗浅的知道这是浮点数的精度问题,具体原因并没有深究过,偶然又碰到一个与这个问题相关的问题,就再捋一下,深层次理解下。
遇到的问题和不理解的地方
- 0.1+0.2 !=0.3
- 安全整数范围MDN上是(-(2^53 - 1) ~ (2^53 - 1)),犀牛书上是 ( -2^53 ~ 2^53 )
- js的最大正数为什么是1.7976931348623157e+308
- js的最小正数为什么是5*10^-324
原因是什么
以上几个问题其实是JS采用的是IEEE754浮点数标准存储, 其他用这种标准的语言都是会有这样的问题的,因此理解这个标准,上面的就都能深层次的理解了。
js中的IEEE754浮点数标准
Js中number类型的数字都是采用64位的双精度浮点数进行存储的,其中1个符号位(0正1负),11个指数位,52个尾数位。
0 01111111111 0000000000000000000000000000000000000000000000000000 ≙ +1.0*2^0 = 1
0 10000000000 1000000000000000000000000000000000000000000000000000 ≙ +1.1*2^1 = 3
复制代码
注意点:
-
指数部分1~2016会被表示为正常的数字
-
指数部分为0且尾数部分不为0用来表示非正常的数字,计算时不会有前导1, 即计算公式为:
3.指数部分为0, 尾数部分为0, 根据不同符号位值表示+0和-0
4.指数部分为2047,尾数部分为0, 根据不同符号位值表示负无穷(Number.NEGATIVE_INFINITY)、正无穷(Number.POSITIVE_INFINITY )
5.指数部分为2047, 尾数部分不为0,值会解析为NAN
疑问解答
* 0.1+0.2 !=0.3
这个问题就是浮点数存储的精度问题,我们知道尾数位只有52位,0.1用二进制表示如下:
0.000110011001100110011001100110011001100110011001100110011...
复制代码
如上可以看出0.1二进制表示时尾数是超过52位的, 所以52位之后的会被舍去,这就有了浮点数存储的精度丢失问题。
* 安全整数范围MDN上是(-(2^53 - 1) ~ (2^53 - 1)),犀牛书上是 ( -2^53 ~ 2^53 )
1个符号位,11位指数位, 52位尾数位。按照分析,不考虑符号位,尾数位取值52个1就是表示的最大值了,不会有精度损失,此时指数位代表数值是52+1023=1075,此时即为(-(2^53 -1)~(2^53 -1) ) 这就是MDN上描述的,这个范围内的值都是没有精度问题的。但是2^53这个值,存储的时候尾数是52个0, 指数位为53+1023=1076,这个值也是刚好没有精度损失的,此时即为(-2^53 ~2^53) ,这就是犀牛书上的描述了。值得注意的是用Math.isSafeInteger()判断安全数范围和MDN中描述一样。
* js中能表示的最大正数
由IEEE754浮点数标准可知, 指数位取:2046, 尾数位取52位1就表示最大值1.7976931348623157*10^308
* js中能表示的最小正数
由IEEE754浮点数标准可知, 指数位全为:0, 尾数位取51位0, 最后一位1就表示最小值 如下图所示,表示出了最大正数与最小正数的以及一些特殊值的64位二进制存储。 附一个一些特殊值的64位存储:wiki。
总结
掌握IEEE754浮点数标准,就能够深层次的理解精度、最大值、最小值以及其他问题, 需要注意IEEE754浮点数标准中的一些特殊情况。