前端应该知道的JavaScript浮点数和大数的原理
栏目: JavaScript · 发布时间: 5年前
内容简介:不知道大家在平时的搬砖中有没有遇到过一些JavaScript数字相关的坑,比如比较经典的0.1+0.2=0.30000000000000004、JavaScript有一个Number.MAX_VALUE还有一个Number.MAX_SAFE_INTEGER等等问题。如果这些问题不了解清楚,业务开发中很有可能会出现一些很奇怪的问题。首先在开始之前需要了解一下JavaScript的number类型在计算机中是如何存储的,这也是一切问题的基础。JavaScript的数字都是number类型的,不管是整数还是浮点数
不知道大家在平时的搬砖中有没有遇到过一些JavaScript数字相关的坑,比如比较经典的0.1+0.2=0.30000000000000004、JavaScript有一个Number.MAX_VALUE还有一个Number.MAX_SAFE_INTEGER等等问题。如果这些问题不了解清楚,业务开发中很有可能会出现一些很奇怪的问题。
几个问题
先抛出几个问题
- 为什么0.1+0.2 != 0.3?
-
为什么1.005.toFixed(2)=1.00而不是1.01
-
为什么会有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER这两个常量同时存在?
接下来就以这三个问题为目的来梳理一下来龙去脉。
双精度存储
首先在开始之前需要了解一下JavaScript的number类型在计算机中是如何存储的,这也是一切问题的基础。JavaScript的数字都是number类型的,不管是整数还是浮点数都以IEEE754双精度的格式存储在计算机中,什么是双精度呢?就是以64个bit位来存储,具体的存储格式是:
分别是 1个符号位+11个指数位+52个尾数位
举个例子,如果是5.5这个数字的话,则计算过程是这样的:
5.5 转二进制 =====> 101.1 科学计数法 =====> 1.011*2^2
存入计算机:
符号位:0
指数位:2 加1023 =====> 1025 转二进制 =====> 10000000001
尾数位:1.011 隐去小数点左边的1 =====> 011
存入计算机,如下图,截图来自IEEE754可视化,感兴趣可以把玩一下
接下来进入第一个问题
为什么0.1+0.2 != 0.3?
在浏览器控制台可以测试一下结果:
下面就剥茧抽丝的讲一下为什么会这样
0.1 转二进制 =====> 0.0001100110011001100...(1100循环)
转科学计数法 =====> 1.100110011...(1100循环) *2^-4
数据是无限循环的,但是可供使用的尾数位却是有限的,只有52位可以使用,所以在第53位会被舍去并且进位
最终在计算机中存储如下图:
类似的0.2在计算机中的存储如下图:
所以最终的计算就是:
0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111
计算结果转换为十进制数字就是0.30000000000000004
所以就是因为,0.1和0.2在计算机中的二进制存储会让它们本身损失掉一定的精度,而它们在计算机中的二进制存储转换成十进制时已经不是真正的0.1和0.2了,相加的结果也就自然不是0.3了。
问题来了,既然0.1在计算机中的存储已经有了舍入误差,那为什么num=0.1能得到0.1呢?
可以在控制台使用toPrecision看一下0.1在不同精度下的返回
可以看出来其实0.1是截断了一部分精度后得到的结果,那么这个问题就可以转化为:双精度浮点数是按什么规则来截断的呢?
在双精度浮点数的英文wiki中可以找到中可以找到这么一段话:
大意是: 如果一个 IEEE 754 的双精度浮点数被转成至少含17位有效数字的十进制数字字符串,当这个字符串转回双精度浮点数时,必须要跟原来的数相同;换句话说,如果一个双精度的浮点数转为十进制的数字时,只要它转回来的双精度浮点数不变,精度取最短的那个就行。
拿0.1来举例子,0.1和0.10000000000000001转成双精度浮点数的存储是一样的,所以取最短的0.1就行了。
接下来是第2个问题
为什么1.005.toFixed(2)=1.00而不是1.01
因为在第一个问题中已经说了,一个十进制数字转为双精度浮点数然后再取出来时,跟原十进制数字可能会有误差,试一下1.005取20个精度:
很明显1.005只是一个被截断后的数字,它的双精度浮点数代表的20位精度的数字是1.0049999999999998934,所以进行保留2位的四舍五入时,2位后的数字会被全部舍去。
为什么会有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER这两个常量同时存在?
可以在控制台看一下:
为什么最大安全整数是2^53-1?前面说到了JavaScript浮点数存储是52位尾数位,但是因为科学计数法小数点左侧的1会在存储时省去,所以52位尾数+省去的1位=53个可表示的位数。
二进制的53位全是1时转换为十进制既是:2^53-1=9007199254740991
哪为什么2^53-1是最大安全整数呢?比它大会怎样?
在浏览器试一下:
以2^53来说明一下为什么2^53-1是最大安全整数,安全在哪里
2^53 转二进制 =====> 100000000000000000000000000000000000000000000000000000(53个0)
转为科学计数法 =====> 1.00000000000000000000000000000000000000000000000000000(53个0)*2^53
存入计算机 =====> 尾数位只有52位所以截掉末尾的0只能存52个0
2^53+1 转二进制 =====> 100000000000000000000000000000000000000000000000000001(52个0)
转为科学计数法 =====> 1.00000000000000000000000000000000000000000000000000001(52个0)
存入计算机 =====> 尾数位只有52位所以截掉末尾的1只能存52个0
可以看出来,2^53和2^53+1在计算机中的存储尾数和指数都相同,所以两个不同的数在计算机中的存储是一样的,这样就非常的不安全了。
所以2^53-1是JavaScript里面的最大安全整数。至于Number.MAX_VALUE,就是把尾数位和指数位都设为1再转为十进制就好了。
如何解决
上面的说到的浮点数运算、舍入运算、大数运算,都可以使用bignumber这个库来解决,打开链接在控制台就可以写demo。
后记
业务中很多难缠的bug往往是基础知识了解的不够深入造成的,了解这些原理可以清晰的知道自己在写什么,并且快速的debug,不至于陷入cv(control+C、control+V)工程师的循环中。
以上所述就是小编给大家介绍的《前端应该知道的JavaScript浮点数和大数的原理》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Web Application Hacker's Handbook
Dafydd Stuttard、Marcus Pinto / Wiley / 2011-9-27 / USD 50.00
The highly successful security book returns with a new edition, completely updated Web applications are the front door to most organizations, exposing them to attacks that may disclose personal infor......一起来看看 《The Web Application Hacker's Handbook》 这本书的介绍吧!