跳至主要內容

int和bigInt

Mr.Dylin...大约 5 分钟JavascriptD_Javascript4.Javascript基础系列数据类型

int 和 bigInt

了解 IEEE745 标准

JS 数字不区别整数与浮点数,只有 Number 类型open in new window,即都是浮点数,它采用 IEEE 754 标准的 64 位双精度格式 (如果是索引数字则是 32 位单精度)。

IEEE 754 的双精度

IEEE 754 标准为 IEEE 二进位浮点数算术标准,是由美国电气电子工程师学会 (IEEE) 发布。IEEE 754 是最广泛使用的浮点数运算标准,为许多 CPU 与浮点运算器所采用,大部分编程语言都有提供 IEEE 格式与算术,但有些将其列为非必要的。

1 位数符 + 11 位阶码 + 52 位尾数

  • 数符 S(sign):标记正负,0 为正,1 为负

  • 阶码 E(exponent bias):阶码 = 阶码真值 + 偏移量 1023。偏移量 = 2^(k-1)-1,k 表示阶码位数

  • 尾数 M(Mantissa):数字的小数部分

V = (-1)^S x 2^(E-1023)*1.M(其中 1023 是偏移量)

32 位单精度: 1 位数符 + 8 位阶码 + 23 位尾数

阶码 E、阶码真值的区别

阶码真值即为科学记数法指数的真实值的 2 进制,它指明了小数点在尾数中的位置,阶码真值与偏移量相加得到阶码。简单理解,阶码真值是实际指数中二进制值,而阶码是指数偏移之后保存起来的二进制数据。

为什么会有偏移量 1023

11 位代表的阶码,如果是无符号是 [0,2047], 去除 0 与 2047 两个非规格化情况 (后面会讲到为什么是非规格化情况),变成 [1,2046], 偏移之前,指数是 [-1022,1023]。如果没有偏移量的存在,指数需引入符号位, 则需引入补码,比较计算更加复杂,为了简化操作,才使用无符号的阶码,引入偏移量的概念。

尾数 M 实际有多少位

同一个浮点数的表示方式有很多,但规范中一般使用科学计数法,例如用 5.123x10^3 表示,而不用 51.23x10^2 或 0.5123x10^4。二进制中只有 0 与 1,那么按科学计数法,首位只可能是 1,对此 IEEE 754 省略了这个默认的 1,所以有效尾数实际上是有 53 位的。


举个例子

十进制 -125.125,在 JS 内存中的二进制数据是多少

第 1 步,负号 S 为 1,绝对值转成二进制:1111101.001,(整数小数分开计算,整数除 2 取余;小数乘 2 取整)

第 2 步,科学计数法:1.11110100 1 * 2^6

第 3 步,计算阶码: 000 00000110 (阶码真值) + 011 11111111 (偏移量1023) = 1 00 00000101

第 4 步,得到最终存储值:1 + 100 00000101 + 11110100 10000000 00000000 00000000 00000000 00000000 0000(共64位)

数字的范围

数的范围有两个概念,一为最大正数与最小负数,二为最小正数与最大负数,即 [最小负数,最 大负数] 并上 [最小正数,最大正数]

从 S、E、M 三个维度看,S 代表正负,E 阶码值远大于 M 尾数个数,所以 E 阶码决定大小,M 尾数决定精度。

从 E 阶码分析:

规格化下,当 E 最大值时, 2046-1023=1023,或 ,111 11111110 - 011 11111111 (偏移量) = 011 11111111 = 1023

从 E 的最大值,从指数来看,得知数值范围是 [-21023,21023],JS 函数 Math.pow(2,1023) 结果是 8.98846567431158e+307,然而如果尾数是,1.11111111 11111111 11111111 11111111 11111111 11111111 1111 ,则其接近于 2,8.98846567431158 x 2 再合上原来的指数,约等于

1.797693134862316e+308

1.7976931348623157e+308,这个值是我们用 JS 常量 Number.MAX_VALUE 获取到的,两者非常相近。

所以数字的范围是 [-1.7976931348623157e+308,1.7976931348623157e+308],如果超过这个值,则数字太大,在 JS 中会显示 Infinity 或 - Infinity, 叫正向溢出。

非规格化下,指数为,000 00000000 - 011 11111111 (偏移量) = - 100 00000001(转成 10 进制,减 1 取反)= - 1023

从指数看,得知最小值是 2^-1023,然而如果尾数是 0.00000000 00000000 00000000 00000000 00000000 00000000 0001

不为 0 的情况,52 位尾数相当于小数点还能虚拟化的向右移动 51,可以取得更小的 2^-51, 所以最小值为 2^-1074 = Math.pow(2,-1074) 约等于 5e-324

而 JS 最小值常量 Number.MIN_VALUE 正是 5e-324

所以 (-5e-324,5e-324) 之间的数比可表示的最小数还要小,显示成 0,叫反向溢出。

整数范围

从 M 尾数分析,精度最多是 53 位,精确整数的范围。

如 M 最大值,1.11111111 11111111 11111111 11111111 11111111 11111111 1111 即 2^53-1 , 9007199254740991

可用 Math.pow(2,53)-1 计算范围 [-9007199254740991, 9007199254740991],

恰好发现:

JS 最小安全整数 Number.MIN_SAFE_INTEGER: -9007199254740991

JS 最大安全整数 Number.MAX_SAFE_INTEGER: 9007199254740991

如果整数是这个或者小数尾数在这个『范围』内,则是安全整数,可用函数 Number.isSafeInteger() 验证。

尾数 M 在 52 位范围内,1. 00000000 00000000 00000000 00000000 00000000 00000000 0001 可以认为是最小精度 2^-52,可用常量 Number.EPSILON 1

2.220446049250313e-16

表示,可用它做精度判断、误差设置等。

JS 中数的总结

JS 的 32 位整数

64 位精度

场景

数组可识别 length 索引、位运算

正常 number

整数范围

数组可识别 length 索引: [0, 4294967295] , 参与位运算的数字范围: [-2147483647, 2147483647]

[-9007199254740991, 9007199254740991]

可表示数的范围

同上

[-1.7976931348623157e+308,-5e-324],[5e-324, 1.7976931348623157e+308]

最小精度

1

2.220446049250313e-16

一目了然,如果超过相关值时,需使用字符串手工运算转换了。又或者使用开源的  bignumber.js

了解 64 位双精度的实现过程之后,Cifer 下面这篇文章就很好理解了:解决 toFixed 四舍五入陷阱

上次编辑于:
贡献者: zddbic