7

JavaScript中最大的数有多大

 4 years ago
source link: http://www.pengrl.com/p/20040/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

之前有前端同学问我,JavaScript中最大的数有多大。

那时就想写一些文章,从整型各种进制的转换,到原码反码补码的形式,最后到浮点数,再加上字符串类型的数字,把计算机世界里与数字相关的内容都说一说。

但由于对相关知识的掌握程度,表达能力,执行力等各方面原因,一直没有动笔。。

今天看到draveness大佬写了一篇 《为什么 0.1 + 0.2 = 0.300000004 · Why’s THE Design?》 ,很好的讲解了浮点数的知识。

所以本文就直接在大佬文章的基础之上讲解就好了。看本文前可以先看看大佬的文章。

正文

JS中Number类型的数字,不管是整数还是小数,底层都使用64位的浮点数形式存储。所以JavaScript中最大的数有多大,等价于64位浮点数最大的数有多大。

浮点数要解决的问题

我们先跳出实现细节,来谈谈为什么浮点数存在精度问题。

比如 0~100 这个范围内,整数的个数是有限的,就是101个。

而如果是小数,由于小数点之后的部分是无限的,比如我随便说两个小数, 1.23041.2300004 ,中间到底出现多少个0都是合法的小数,所以理论上是没办法使用有限的存储空间(比如64位)表示完所有的小数。

你可能很容易想到,限制小数点后的位数,比如最多两位,也即范围是 0.00~0.99 ,那么 0~100 范围内的数就变回有限了,也即 101*100=10100 个。

这种方式适用一些场景,比如人民币,如果单位是元,那么小数只需要两位,分别是角和分。

可惜的是,并不是所有场景,小数点后保留两位就够用,关于这点相信也不用我过多举例,拿数字 3.1415 来说,只能存储为 3.143.15 ,也即精度丢失了。

并且,如果总是预留一部分空间存储两位小数,那么也是一种浪费。

抽象来看,我们面临的问题实际上是,如何用有限的空间存储尽量大的数字范围,以及尽量高的精度。

某种角度,浮点数是一种解决上述问题的编码方式。

浮点数的原理

JS和大多数编程语言一样,采用 IEEE 754 浮点数标准。

在draveness的 文章 中,图文并茂的对该标准进行了描述,并分别举了 0.10.20.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中最大的数有多大 这个问题,这其实包含两个问题:

  1. JavaScript Number类型中,最大的那个正整数是多少(也即超过这个数就没法表示了)
  2. 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 )

版权声明:本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。

ruq2Mbu.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK