内容简介:之前有前端同学问我,JavaScript中最大的数有多大。那时就想写一些文章,从整型各种进制的转换,到原码反码补码的形式,最后到浮点数,再加上字符串类型的数字,把计算机世界里与数字相关的内容都说一说。但由于对相关知识的掌握程度,表达能力,执行力等各方面原因,一直没有动笔。。
之前有前端同学问我,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 * power(2, 0) * 1 2 -> 1 * power(2, 1) * 1 3 -> 1 * power(2, 1) * (1 + 1/power(2, 1)) 4 -> 1 * power(2, 2) * 1 5 -> 1 * power(2, 2) * (1 + 1/power(2, 2)) 6 -> 1 * power(2, 2) * (1 + 1/power(2, 1)) 7 -> 1 * power(2, 2) * (1 + 1/power(2, 1) + 1/power(2, 2)) 8 -> 1 * power(2, 3) * 1 1 -> 0 01111111 00000000000000000000000 二进制01111111 = 十进制127,平移后得到exp = 0 fraction = 0 7 -> 0 10000001 11000000000000000000000 二进制10000001 = 十进制129,平移后得到exp = 2 fraction前两位有值,所以是1/power(2, 1) + 1/power(2, 2)
在 这个非常棒的网站 中,你可以输入任意数字,查看对应的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做个试验,验证下:
> console.log(Number.MAX_SAFE_INTEGER) 9007199254740991 > console.log(Number.MAX_SAFE_INTEGER+1) 9007199254740992 > console.log(Number.MAX_SAFE_INTEGER+2) 9007199254740992 > console.log(Number.MAX_SAFE_INTEGER+3) 9007199254740994
所以写JS的同学们要注意,Number超过这个值后,可能会出现bug哦。
原文链接: https://pengrl.com/p/20040/
原文出处: yoko blog ( https://pengrl.com )
原文作者: yoko ( https://github.com/q191201771 )
版权声明:本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。