写在前面
之前有前端同学问我,JavaScript中最大的数有多大。
那时就想写一些文章,从整型各种进制的转换,到原码反码补码的形式,最后到浮点数,再加上字符串类型的数字,把计算机世界里与数字相关的内容都说一说。
但由于对相关知识的掌握程度,表达能力,执行力等各方面原因,一直没有动笔。。
今天看到draveness大佬写了一篇《为什么 0.1 + 0.2 = 0.300000004 · Why’s THE Design?》,很好的讲解了浮点数的知识。
所以本文就直接在大佬文章的基础之上讲解就好了。看本文前可以先看看大佬的文章。
正文
JS中Number类型的数字,不管是整数还是小数,底层都使用64位的浮点数形式存储。所以JavaScript中最大的数有多大,等价于64位浮点数最大的数有多大。
浮点数要解决的问题
我们先跳出实现细节,来谈谈为什么浮点数存在精度问题。
比如0~100
这个范围内,整数的个数是有限的,就是101个。
而如果是小数,由于小数点之后的部分是无限的,比如我随便说两个小数,1.2304
和1.2300004
,中间到底出现多少个0都是合法的小数,所以理论上是没办法使用有限的存储空间(比如64位)表示完所有的小数。
你可能很容易想到,限制小数点后的位数,比如最多两位,也即范围是0.00~0.99
,那么0~100
范围内的数就变回有限了,也即101*100=10100
个。
这种方式适用一些场景,比如人民币,如果单位是元,那么小数只需要两位,分别是角和分。
可惜的是,并不是所有场景,小数点后保留两位就够用,关于这点相信也不用我过多举例,拿数字3.1415
来说,只能存储为3.14
或3.15
,也即精度丢失了。
并且,如果总是预留一部分空间存储两位小数,那么也是一种浪费。
抽象来看,我们面临的问题实际上是,如何用有限的空间存储尽量大的数字范围,以及尽量高的精度。
某种角度,浮点数是一种解决上述问题的编码方式。
浮点数的原理
JS和大多数编程语言一样,采用IEEE 754
浮点数标准。
在draveness的文章中,图文并茂的对该标准进行了描述,并分别举了0.1
,0.2
,0.15625
的例子。建议先看看那篇文章。
浮点数的公式是sign * power(2, exp) * (1 + fraction)
。
对于32位浮点数,sign占1位,exp占8位,fraction占23位:
- sign占1位,没什么好说的,浮点数都是有符号类型,该位为0时,是正数,也即公式中的sign为1。该位为1时,是负数,也即公式中的sign为-1
- exp占8位,总共可表示256个数字,范围是
[0, 255]
,0和255有特殊用途,我们不展开讲,那么还剩下[1, 254]
,由于浮点数除了支持特别大的数,还要取倒数用于支持特别小的数,所以exp有正有负,这8位的[1, 254]
会平移映射成[-126, 127]
的exp - fraction占23位,这23位中不为0的位就要加上
1/power(2, index)
,index从左到右取值为[1, 23]
,计算得到公式中的fraction
我们补充看一些正整数的例子加深理解:
1 | 1 -> 1 * power(2, 0) * 1 |
在这个非常棒的网站中,你可以输入任意数字,查看对应的32位浮点数是如何表示的。
浮点数的范围
回到JavaScript中最大的数有多大
这个问题,这其实包含两个问题:
- JavaScript Number类型中,最大的那个正整数是多少(也即超过这个数就没法表示了)
- JavaScript Number类型能保证精度的正整数范围是多少(也即该范围内的正整数是可完整连续表示的)
听着有点拗口,举个例子就明白了。假设某种表示方式只能存储1, 2, 3, 100
这4个正整数,那么第一个问题是100,第二个问题是3。
由于32位和64位浮点数的算法部分是一样的,大部分资料为了简洁,都采用32位讲解浮点数。
我们回到JS中的Number类型,底层使用的是64位浮点数,其中11位是指数部分,52位是小数部分。
指数部分11位,总共可表示2048个数字,范围是[0, 2047]
,刨去0和2047,剩下[1, 2046]
,再映射成[-1022, 1023]
。
对于问题一,指数部分和小数部分都取最大值,即
power(2, 1023) * (1 + 1/power(2, 1) + 1/power(2, 2) + ... + 1/power(2, 51) + 1/power(2, 52))
,结果会接近power(2, 1024)
。
注意,这里由于1023大于52,所以exp和fraction可以都取最大值,计算后的结果依然是整数。
对于问题二,实际上是受小数部分影响,即exp取52,fraction取最大值,也即
power(2, 53) - 1
,结果为9007199254740991
,这个数字有16位。
另外,JS中定义了一个常量Number.MAX_SAFE_INTEGER
,它的值就是9007199254740991
。
最后,我们再拿JS做个试验,验证下:
1 | > console.log(Number.MAX_SAFE_INTEGER) |
所以写JS的同学们要注意,Number超过这个值后,可能会出现bug哦。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20040/