浮点数的表⽰⽅法
简单回顾⼀下,简单来说,⽤定点数表⽰数字时,会约定⼩数点的位置固定不变,整数部分和⼩数部分分别转换为⼆进制,就是定点数的结果。
但⽤定点数表⽰⼩数时,存在数值范围、精度范围有限的缺点,所以在计算机中,我们⼀般使⽤「浮点数」来表⽰⼩数。
这篇⽂章,我们就来详细看⼀下浮点数到底是如何表⽰⼩数的,以及浮点数的的范围和精度有多⼤。
什么是浮点数?
⾸先,我们需要理解什么是浮点数?
之前我们学习了定点数,其中「定点」指的是约定⼩数点位置固定不变。那浮点数的「浮点」就是指,其⼩数点的位置是可以是漂浮不定的。
这怎么理解呢?
其实,浮点数是采⽤科学计数法的⽅式来表⽰的,例如⼗进制⼩数 8.345,⽤科学计数法表⽰,可以有多种⽅式:
8.345 = 8.345 * 10^0
8.345 = 83.45 * 10^-1
8.345 = 834.5 * 10^-2
...
看到了吗?⽤这种科学计数法的⽅式表⽰⼩数时,⼩数点的位置就变得「漂浮不定」了,这就是相对于定点数,浮点数名字的由来。
使⽤同样的规则,对于⼆进制数,我们也可以⽤科学计数法表⽰,也就是说把基数 10 换成 2 即可。
浮点数如何表⽰数字?
我们已经知道,浮点数是采⽤科学计数法来表⽰⼀个数字的,它的格式可以写成这样:
V = (-1)^S * M * R^E
其中各个变量的含义如下:
S:符号位,取值 0 或 1,决定⼀个数字的符号,0 表⽰正,1 表⽰负
M:尾数,⽤⼩数表⽰,例如前⾯所看到的 8.345 * 10^0,8.345 就是尾数
R:基数,表⽰⼗进制数 R 就是 10,表⽰⼆进制数 R 就是 2
E:指数,⽤整数表⽰,例如前⾯看到的 10^-1,-1 即是指数
如果我们要在计算机中,⽤浮点数表⽰⼀个数字,只需要确认这⼏个变量即可。
假设现在我们⽤ 32 bit 表⽰⼀个浮点数,把以上变量按照⼀定规则,填充到这些 bit 上就可以了:
假设我们定义如下规则来填充这些 bit:
符号位 S 占 1 bit
指数 E 占 10 bit
尾数 M 占 21 bit
按照这个规则,将⼗进制数 25.125 转换为浮点数,转换过程就是这样的(D代表⼗进制,B代表⼆进制):
1. 整数部分:25(D) = 11001(B)
2. ⼩数部分:0.125(D) = 0.001(B)
所以符号位 S = 0,尾数 M = 1.001001(B),指数 E = 4(D) = 100(B)。
按照上⾯定义的规则,填充到 32 bit 上,就是这样:
浮点数的结果就出来了,是不是很简单?
但这⾥有个问题,我们刚才定义的规则,符号位 S 占 1 bit,指数位 E 占 10 bit,尾数 M 占 21 bit,这个规则是我们拍脑袋随便定义出来的。
如果你也想定⼀个新规则,例如符号位 S 占 1 bit,指数位 E 这次占 5 bit,尾数 M 占 25 bit,是否也可以?当然可以。
按这个规则来,那浮点数表⽰出来就是这样:
我们可以看到,指数和尾数分配的位数不同,会产⽣以下情况:
1. 指数位越多,尾数位则越少,其表⽰的范围越⼤,但精度就会变差,反之,指数位越少,尾数位则越多,表⽰的范围越⼩,但精度就
会变好
2. ⼀个数字的浮点数格式,会因为定义的规则不同,得到的结果也不同,表⽰的范围和精度也有差异
早期⼈们提出浮点数定义时,就是这样的情况,当时有很多计算机⼚商,例如IBM、微软等,每个计算机⼚商会定义⾃⼰的浮点数规则,不同⼚商对同⼀个数表⽰出的浮点数是不⼀样的。
这就会导致,⼀个程序在不同⼚商下的计算机中做浮点数运算时,需要先转换成这个⼚商规定的浮点数格式,才能再计算,这也必然加重了计算的成本。
那怎么解决这个问题呢?业界迫切需要⼀个统⼀的浮点数标准。
浮点数标准
直到1985年,IEEE 组织推出了浮点数标准,就是我们经常听到的 IEEE754 浮点数标准,这个标准统⼀了浮点数的表⽰形式,并提供了 2种浮点格式:
单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit
为了使其表⽰的数字范围、精度最⼤化,浮点数标准还对指数和尾数进⾏了规定:
1. 尾数 M 的第⼀位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表⽰了 24 位
有效数字,双精度 52 位尾数可以表⽰ 53 位有效数字
2. 指数 E 是个⽆符号整数,表⽰ float 时,⼀共占 8 bit,所以它的取值范围为 0 ~ 255。但因为指数可以是负的,所以规定在存⼊ E
时在它原本的值加上⼀个中间数 127,这样 E 的取值范围为 -127 ~ 128。表⽰ double 时,⼀共占 11 bit,存⼊ E 时加上中间数1023,这样取值范围为 -1023 ~ 1024。
除了规定尾数和指数位,还做了以下规定:
指数 E ⾮全 0 且⾮全 1:规格化数字,按上⾯的规则正常计算
指数 E 全 0,尾数⾮ 0:⾮规格化数,尾数隐藏位不再是 1,⽽是 0(M = 0.xxxxx),这样可以表⽰ 0 和很⼩的数
指数 E 全 1,尾数全 0:正⽆穷⼤/负⽆穷⼤(正负取决于 S 符号位)
指数 E 全 1,尾数⾮ 0:NaN(Not a Number)
标准浮点数的表⽰
有了这个统⼀的浮点数标准,我们再把 25.125 转换为标准的 float 浮点数:
1. 整数部分:25(D) = 11001(B)
2. ⼩数部分:0.125(D) = 0.001(B)
所以 S = 0,尾数 M = 1.001001 = 001001(去掉1,隐藏位),指数 E = 4 + 127(中间数) = 135(D) = 10000111(B)。填充到 32 bit 中,如下:
这就是标准 32 位浮点数的结果。
如果⽤ double 表⽰,和这个规则类似,指数位 E ⽤ 11 bit 填充,尾数位 M ⽤ 52 bit 填充即可。
浮点数为什么有精度损失?
我们再来看⼀下,平时经常听到的浮点数会有精度损失的情况是怎么回事?
如果我们现在想⽤浮点数表⽰ 0.2,它的结果会是多少呢?
0.2 转换为⼆进制数的过程为,不断乘以 2,直到不存在⼩数为⽌,在这个计算过程中,得到的整数部分从上到下排列就是⼆进制的结果。
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
浮点型变量float0.2 * 2 = 0.4 -> 0(发⽣循环)
...
所以 0.2(D) = 0.00110…(B)。
因为⼗进制的 0.2 ⽆法精确转换成⼆进制⼩数,⽽计算机在表⽰⼀个数字时,宽度是有限的,⽆限循环的⼩数存储在计算机时,只能被截断,所以就会导致⼩数精度发⽣损失的情况。
浮点数的范围和精度有多⼤?
最后,我们再来看⼀下,⽤浮点数表⽰⼀个数字,其范围和精度能有多⼤?
以单精度浮点数 float 为例,它能表⽰的最⼤⼆进制数为 +1.1.11111…1 * 2^127(⼩数点后23个1),⽽⼆进制 1.11111…1 ≈ 2,所以 float 能表⽰的最⼤数为 2^128 = 3.4 * 10^38,即 float 的表⽰范围为:-3.4 * 10^38 ~ 3.4 * 10 ^38。
它能表⽰的精度有多⼩呢?
float 能表⽰的最⼩⼆进制数为 0.0000….1(⼩数点后22个0,1个1),⽤⼗进制数表⽰就是 1/2^23。
⽤同样的⽅法可以算出,double 能表⽰的最⼤⼆进制数为 +1.111…111(⼩数点后52个1) * 2^1023 ≈ 2^1024 = 1.79 *
10^308,所以 double 能表⽰范围为:-1.79 * 10^308 ~ +1.79 * 10^308。
double 的最⼩精度为:0.0000…1(51个0,1个1),⽤⼗进制表⽰就是 1/2^52。
从这⾥可以看出,虽然浮点数的范围和精度也有限,但其范围和精度都已⾮常之⼤,所以在计算机中,对于⼩数的表⽰我们通常会使⽤浮点数来存储。
总结
这篇⽂章我们主要讲了数字的浮点数表⽰⽅式,总结如下:
1. 浮点数⼀般⽤科学计数法表⽰
2. 把科学计数法中的变量,填充到固定 bit 中,即是浮点数的结果
3. 在浮点数提出的早期,各个计算机⼚商各⾃制定⾃⼰的浮点数规则,导致不同⼚商对于同⼀个数字的浮点数表⽰各不相同,在计算时
还需要先进⾏转换才能进⾏计算
4. 后来 IEEE 组织提出了浮点数的标准,统⼀了浮点数的格式,并规定了单精度浮点数 float 和双精度浮点数 double,从此以后各个计
算机⼚商统⼀了浮点数的格式,⼀直延续⾄今
5. 浮点数在表⽰⼩数时,由于⼗进制⼩数在转换为⼆进制时,存在⽆法精确转换的情况,⽽在固定 bit 的计算机中存储时会被截断,所
以浮点数表⽰⼩数可能存在精度损失
6. 浮点数在表⽰⼀个数字时,其范围和精度⾮常⼤,所以我们平时使⽤的⼩数,在计算机中通常⽤浮点数来存储