JavaScript的64位浮点数
一、JavaScript的64位浮点数
JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。
所以,1与1.0是相同的,是同一个数。
1 === 1.0 // true
这就是说,JavaScript 语言的底层根本没有整数,所有数字都是小数(64位浮点数)。容易造成混淆的是,某些运算只有整数才能完成,此时 JavaScript 会自动把64位浮点数,转成32位整数,然后再进行运算。
由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。
0.1 + 0.2 === 0.3
// false
0.3 / 0.1
// 2.9999999999999996
二、IEEE754 浮点数标准的制定背景
早期人们提出浮点数定义时,每个计算机厂商会定义自己的浮点数规则,不同厂商对同一个数表示出的浮点数是不一样的。
这就会导致,一个程序在不同厂商下的计算机中做浮点数运算时,需要先转换成这个厂商规定的浮点数格式,才能再计算,这也必然加重了计算的成本。
于是,1985年,IEEE 组织推出了浮点数标准,就是我们经常听到的 IEEE754 浮点数标准,这个标准统一了浮点数的表示形式,并提供了 2 种浮点格式:
单精度浮点数 float:32 位,符号位 s 占 1 bit,指数 e 占 8 bit,小数数 f 占 23 bit
双精度浮点数 float:64 位,符号位 s 占 1 bit,指数 e 占 11 bit,小数 f 占 52 bit
三、JavaScript浮点数在内存中的结构
根据国际标准IEEE 754 ,JavaScript 浮点数的64个二进制位,从最左边开始,是这样组成的:
第一部分(蓝色):用来存储符号位(sign),第1位:符号位,0表示正数,1表示负数
第二部分(绿色):用来存储指数(exponent),第2位到第12位(共11位):指数部分
第三部分(红色):用来存储小数(fraction),第13位到第64位(共52位):小数部分(即有效数字)
符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。
四、 64位浮点数表示数字的公式
浮点数是采用科学计数法来表示一个数字的,它的格式可以写成这样:
(-1)^s f 2^e
符号部分 -1 or 1
f的范围为1<=f<2 使用52位表示
指数有正有负,指数位长度为11比特,所以能表示的数字范围为0~2047
假设我们将十进制数 25.125 转换为浮点数,转换过程就是这样的(D代表十进制,B代表二进制):
整数部分:25(D) = 11001(B)
小数部分:0.125(D) = 0.001(B)
用二进制科学计数法表示:25.125(D) = 11001.001(B) = 1.1001001 * 2^4(B)
所以符号位 s = 0,小数 f = 1.001001(B) = 001001(去掉1,隐藏位),指数 e = 4+1023(中间值) = 1027(D) = 10000000011(B)。
按照内存结构中浮点数定义的规则,填充到 64 bit 上。
五 、52位为什么可以表示53位小数(精度)
IEEE754规定小数部分第一位隐含为1,不写,因为所有二进制第一个有效数字都是1。
所以加上省略的1位,精度位数是 53 bit。所以在 0 ~ 2^53 内的整数都是有效数字,算上第1位符号位,就可以得到 -2^53 ~ 2^53 都是有效数字。
六、浮点数能精确表示的范围
Math.pow(2,53) - 1 // 最大
Number.MAX_SAFE_INTEGER // 常数表示
- (Math.pow(2,53) - 1) // 最大
Number.MIN_SAFE_INTEGER // 常数表示
七、Number的MAX_VALUE
我们知道了 js 中数的表示方法,那么他能表示的最大的数是多少呢,聪明的你肯定会想到是下面这个数:
0 11111111111 1111111111111111111111111111111111111111111111111111
但是,这种情况在 IEEE754 标准中表示 NaN,最大的数其实是:
0 11111111110 1111111111111111111111111111111111111111111111111111
转换成二进制的科学计数法表示如下:
1.1111111111111111111111111111111111111111111111111111 * 2^(2046 - 1023)
= 1.1111111111111111111111111111111111111111111111111111 * 2^1023
= 11111111111111111111111111111111111111111111111111111 * 2^971
= (2^53 - 1) * 2^971
= 1.7976931348623157e+308
我们在浏览器调试窗口里面验证下:
(Math.pow(2, 53) - 1) * Math.pow(2, 971) // 1.7976931348623157e+308
(Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE // true
八、Number的MAX_SAFE_INTEGER
MAX_SAFE_INTEGER 表示在 JavaScript 中最大的安全整数。所谓的安全,就是大于这个数的整数不一定可以精确表示。他的值其实是 2^53 - 1,表示成二进制为:
0 10000110100 1111111111111111111111111111111111111111111111111111
表示成二进制的科学计数法为:
1.1111111111111111111111111111111111111111111111111111 * 2^52
= 11111111111111111111111111111111111111111111111111111
比这个数大一的数为:
100000000000000000000000000000000000000000000000000000
= 1.00000000000000000000000000000000000000000000000000000 * 2^53
在计算机中表示成:
0 10000110101 0000000000000000000000000000000000000000000000000000 0
注意到我们省去掉了一位,按照向偶舍入的规则,不会产生进位。所以这个数还是可以精确表示的,没有问题。
我们再来看看比 MAX_SAFE_INTEGER 大二的数:
100000000000000000000000000000000000000000000000000001
= 1.00000000000000000000000000000000000000000000000000001 * 2^53
在计算机中表示成:
0 10000110101 0000000000000000000000000000000000000000000000000000 1
注意到我们省去掉了一位,按照向偶舍入的规则,还是不会产生进位。这个时候就有问题了,这个数跟刚才那个数竟然是相等的,我们来验证下:
const a = Number.MAX_SAFE_INTEGER
a + 1 === a + 2 // true
所以,在进行大数的相关运算的时候要小心了,最好是使用 BigInt 类型。
评论 (0)