一、引子
上个小节中,我们学习了浮点数的基本构成还有原理,浮点数大致上分为阶码、还有尾数这样的两个部分。上个小节的末尾我们提到过,如果不能确定一个统一的规则,阶码占多少位,尾数占多少位,各自采用原码、补码,还是移码来表示。
如果我们不能统一一个规则,那么在计算机之间进行数据传输,就会出现一些解析方面的困难和问题。
所以有这样的一个组织,它定了一个标准,叫 IEEE 754。很多同学不会读这个组织的名字。
我们来看一下大家熟悉的王者荣耀游戏里边,如果你有双杀,他会说 double kill,对吧?如果三杀 triple kill,四杀 quarter kill。刚才我们念IEEE,把它念作IEEE,其实就是 3 个 E 的意思。
所以大家要知道我们计算机领域很重要的组织的名字,应该怎么优雅地把它念出来,需要学习一下。
二、移码
由于我们 IEEE 754的标准当中,阶码是用移码的方式来表示的,所以这儿我们先快速的回顾一下我们之前学习过的移码。
(1)移码与补码
之前我们说过移码在补码的基础上把符号位取反就可以,并且移码只能表示整数,我们浮点数的阶码部分也只需要用整数来表示,所以移码的特性是能够用来表示阶码的。
来快速回顾一下。对于+19
这个数,它的补码形式是这样的(如下),把它的符号为取反,就可以得到与之对应的一个移码。
-19
这个数,同样的,把它的补码形式的符号位取反,就可以得到与之对应的移码。如下:
(2)移码本身
之前我们用这种方式能够比较简单快速地了解移码和补码之间的联系。
但事实上,如果我们回到移码的定义本身,我们会发现移码最原生的定义是这样的。
要确定移码的机器数表示是这么来确定的。
1.-127
我们会用二进制的形式写出想要表示的数的真值,再加上一个所谓的偏置值。
像我们之前提到的这个例子当中,8位的移码取了偏置值为128。如果用二进制表示,就是1个 1加7 个0。128也就是 2 的 n 减 1 次方。这的 n 指的是移码的总位数, 2 的 7 次方刚好等于128。
所以对于真值-127 来说,我们先写出它的二进制表示,现在我们要求它的移码,只需要用真值的二进制数加上刚才提出的偏置值,也就是1000 0000,这就相当于用 128 减掉 7 个1。二进制减法很简单,最终减出来的结果应该是 7 个 0、1 个1。
所以负的 127 真值它所对应的移码就是这样的值。
2.-3
再来看,比如- 3。
-3如果我们用二进制表示它的真值是-11,用真值加上偏置值,可以得到这个数的移码。
这个加法我们可以把它转换成 10000000减掉11。最后算出来的结果应该是这样的一个数。
和我们在补码的基础上把符号位取反,所得到的移码结果是一致的。
剩下的就不再一个一个举例,大家可以再试一下。
3.偏置值
普通情况
总之,当我们在确定移码的表示方案的时候,其实首先要确定的是偏置值。
我们需要在二进制数的真值基础上加上偏置值,就可以得到基于偏置值所对应的移码。一般来说,偏置值我们都会取 2 的 n 减 1 次方。
基于这种方案,我们得到的移码刚好就可以呈现出之前提到的这种规律。就是只需要在补码的基础上符号位取反。
这是普通的情况。
特殊情况
但是也有时候我们可以把偏置值设为不一样的值。
比如这个小节要学习的 IEEE 754标准里边,它的阶码是用移码来表示的,但是它规定偏置值不是128,而是127,也就是2的 n 减1次方,再减掉一个1。
如果用二进制表示,就是0111 1111。
基于新的偏置值,我们来看一下。
①-128
对于真值- 128 来说,我们先写出它的二进制形式,然后加上偏置值。
大家会发现这儿偏置值的绝对值比- 128 的绝对值要更小。这个加法操作怎么做?
被减数要比减数更小,但是由于移马的位数只有8比特,也就是只有8位,所以所有的这些加减运算其实在背后都默认了会进行模2的 8 次方。会有这样的一个隐含条件。
所以之前我们简单地介绍过模运算。
当我们在模 2 的 8 次方条件下进行加减法的时候,可以在原有的基础上再加上一个 2 的 8 次方,也就是一个 1000 00000。
加上这样的一个值之后,整体的减法得到的结果是不会变的。所以被减数加上一个 1000 00000,得到的结果就是一个1再加上刚才的被减数(01111111),即1011 11111。
当我们加上这个模数之后,就可以保证被减数现在是比减数更大的。
接下来我们就可以用大家熟悉的那种减法运算的规则来计算它的值。
1- 0 等于1,后边的这几位全部都是1;到最高位这个地方是0,需要向高位借一个1,2减1剩1,所以-128它的二进制真值加上偏置值,最终得到的结果就是1111 1111。
因此,如果我们的移码偏置值等于127,那么- 128 这个真值所对应的已码刚好就是 8 个1。
②-127
接下来- 127 。
它的二进制真值应该是负的 7 个1,这个真值加上偏置值,刚好就可以得到 8 个0。
因此,- 127 它所对应的移码应该是 8 个0。
其他的这些真值也是一样的,大家可以自己再验证一下。
总之,当我们把偏置值从 2 的 n 减 1 次方变成 2 的 n 减 1 次方再减 1 之后,相当于从我们之前熟悉的这种移码做了一个减 1 的操作。
所以你看,和之前的这种移码的编码方案相比较,它们之间就只差一格。
另外,最小的两个负数,也就是- 128 和-127,刚好对应两个很特殊的状态全 1 和全0。
如果把这 8 个比特看作一个无符号数,所有的数都是1,这对应无符号数的 255。
-126这个数它所对应的移码。如果把它看作无符号数,应该是无符号数的1。
从无符号数的 1 一直到无符号数的254。整个范围无符号数的数值越大,与之对应的移码的真值也是越大,逐步递增的。
这个地方大家需要注意全 1 和全 0 这两种比较特殊的移码状态。
三、IEEE 754标准
回顾了移码之后,我们接下来正式开始学习 IEEE 754标准它所规定的浮点数的格式。
(1)格式
它分为短浮点数、长浮点数和临时浮点数。
c 语言里边的 float 型变量,它就遵循了这个标准所规定的短浮点数的要求。 c 语言里的 double 类型,对应了标准里边所规定的长符点数,还有一个大家可能没有用过 c 语言里的 long double,这个类型对应的是临时浮点数类型。
<1> 数符
从上至下,这些浮点数的长度会越来越长。短符点数总共只需要 32 个比特位。其中最高位是数符
,表示了整个浮点数的正负性。
<2> 阶码
接下来的 8 个比特位表示的是阶码
。刚才我们说过,阶码是用移码来表示的,并且在这里边,移码的偏置值,它是规定了是127,而不是我们之前熟悉的128。
<3> 尾数
除了数符和阶码之外,末尾还有 23 个比特位表示尾数
。
值得注意的是,尾数是用原码来表示的。
而上一小节我们举的那些例子,尾数都是用补码来表示的。
我们之前说过,对于一个浮点数来说,如果它的尾数是用原码表示,那么我们会希望它的第一个有效的数值位是1,也就是所谓浮点数规格化的问题。
我们干脆可以默认,在这一部分尾数之前,我们让它隐含了一个最高位的1,这样就免去了我们必须要规格化这样的一个步骤。
所以,虽然短符点数它的尾数只有 23 位,但事实上它应该总共是有 24 位有效位。因为我们默认了前边隐含了一个1。1.M
才是尾数真正表示的数值。
所以这一点在做题的时候需要注意,要在尾数前边加上一个1,小数点是跟在 1 的后面。
除了尾数之外,刚才说过,阶码是用移码来表示的,移码的偏置值和我们之前熟知的 2 的 n 次方减 1 这种偏置值不一样,还要再减一个1。
另外,刚才我们说过,8位的移码可以表示的范围应该是负的 128 一直到正的 127 。
但是当偏置值为 127 的时候,- 128 和- 127 这两个数它们的移码表示会比较特殊,一个是全0,一个是全1。
在 754 标准中,我们会把阶码全 1 和全 0 这两个状态作为一个特殊的用途。
所以事实上八位的阶码正常来说,它的真值的范围应该只会取到负的 126 到正的127。因为全 1 对应-128,全 0 对应-127。
这两个状态我们会用作特殊的处理。具体怎么特殊,我们之后再来补充。
现在先来看我们有可能会遇到的正常情况,对于表里边给出的每一种浮点数,数符、阶码、尾数各自占多少位,这一点大家一定要记住。
考试的时候不会告诉你短浮点数,它的格式是 1 + 8 + 23+32,这是需要大家自己记住的。
(2)类型
刚才介绍了IEEE 754标准:
现在我们来看几个比较直观的例子。
1.短浮点数
它最开始的这一位是数符(红色),表示整个数值的正负性;
接下来的8位(蓝色)表示的是阶码,阶码是用移码的方式来表示的。之前我们说过移码应该是等于真值加上偏置值。
所以基于这个定义,我们可以得出这样的结论。要确定阶码的真值,应该要用移码减掉偏置值,对于短浮点数来说,就是要减掉127。
因此短浮点数它的真值应该是多少呢?
首先是用符号位确定它的正负性:
尾数应该是1.
,再加上这后边的 23 位:
最后再乘以 2 的阶码减掉偏置值这么多次方,这就是短浮点数的真值确定方式。
2.double型
接下来再看 double 型的浮点数。
这种浮点数,它的总位数要比单精度的要多一倍,总共有 64 位。
它最开始的这一位是符号位(红色),接下来的 11 位表示的是阶码(蓝色),再往后还有 52 位表示的是尾数(绿色)。
同样的,我们需要在尾数的前边加上一个1.M
,这个 1 是隐含的。
由于阶码总共有 11 位,长浮点数或者这种双精度浮点数,它的偏置值应该是 2 的 11 次方减1再减1,也就是 2 的 10 次方减1,应该是1023。
如果用二进制来表示1023,我们可以把它写成1个0加上 10 个1,这就是 1023 的二进制表示。
所以如果我们要确定长浮点数,它的阶码的真值是多少,我们需要用蓝色部分的移码减掉刚才提出的偏移量偏置值。
计算的时候,可以把移码部分,还有偏置值部分,把它们看作是无符号数。把这些二进制转换成无符号数,再来进行相减的操作。没有必
要一定要用两个二进制数相减。
比如在这个例子当中,移马我们如果把它看作无符号数,应该是000 0001 1100,等于28,这是移马看作无符号数的值28;再减掉偏置值1023,大家可以自己算一下,这就是阶码的真值。
总之,这儿想强调的是,我们在实际计算的时候,可以先把移码看作是无符号数,把它转换成十进制,再来减掉偏移量。这样要比直接用二进制来进行减法会更简单一些,也更不容易出错。
和短浮点数的真值确定方式也是类似的。长浮点数的真值,我们首先根据符号位确定整体的正负性;尾数应该是1.
加上后边的 52 个比特的信息(绿色部分);再乘以 2 为底,阶码减 1023次方 这样的值,这就是长浮点数的真值。
(3)案例
1.案例一
现在我们要把十进制的- 0. 75 转变成754标准规定的单精度浮点数的类型。
应该是 1 + 8 + 23 ,即1 位数浮符,8位阶码, 23 位尾数。
①转换为二进制
首先我们把这个十进制数转换成二进制的形式,应该是负的 0. 11。
②规格化
由于尾数部分最高位隐含为1,所以我们必须对刚才得到的二进制数进行一个规格化,让小数点前边这一位变为1,这样才可以和隐含的高位 1 进行一个对应。
所以负的 0. 11 我们可以把它看作是负的 1. 1再乘以 2 的负1次方,这就意味着小数点要前移一位,这样我们就完成了规格化。
③数符
由于这个数它是一个负数,所以数符我们需要取1,1表示负。
④尾数
尾数部分应该是一个1,后边的几位全部都是0,因为我们在高位已经隐含了一个1,所以再补上高位 1 应该是 1. 10000...,和我们规格化得到的尾数是能够对应上的。
⑤阶码
确定了数符和尾数之后,需要再确定阶码。
阶码的真值应该是- 1 。
⑥移码表示
单精度浮点型,它的偏移量应该是127,所以移码可以用阶码的真值加上偏移量,就可以得到移码的表示。
我们是直接用二进制数相加,但事实上和之前一样,我们也可以直接用十进制相加,再把十进制转换成对应的二进制无符号数。
127加上-1,也就是127减掉 1 等于 126,它所对应的无符号数应该是0111 1110这样的值。
所以看大家自己的习惯,你可以直接用十进制计算,再把十进制转换成与之对应的二进制。只不过是需要把它看作是无符号数,并且总共占 8 个比特。
好,到此为止,我们就确定了数符、移马还有尾数分别应该为多少。把它们拼起来,总共凑足 32 位。这就是用 754 标准的单精度浮点数来表示的- 0. 75 值。
2.案例二
刚才这个例子是从十进制转换成浮点数的,接下来我们再来看一个逆过来转换的例子。
这给出了一个单精度浮点数,用 16 进制表示。问我们它所对应的 10 进制真值是多少。
①转换为二进制
先把 16 进制数转换成与之对应的二进制。
最开始的 1 位是数符,接下来的8位是阶码,再往后的 23 位是尾数。
②分析
把这三个部分分开来分析。
首先数符等于1,说明这是一个负数。
接下来尾数部分应该是.01
后续都是0。在尾数之前还隐含了一个最高位1,所以尾数它所对应的真实的值应该是 1. 01。需要在小数点前面补一个1。
再来看阶码部分。阶码是用移码的方式来表示的,如果我们把它看作是无符号数,就应该对应 129。
③阶码真值
由于单精度浮点数的偏移量或者偏置值是127,所以阶码所对应的真值应该是移码减掉偏移量或者偏置值,也就是 129 减掉 127 等于 2。因此阶码对应的真值就是十进制的2。
④浮点数真值
所以整个浮点数的真值应该是二进制的- 1. 01乘以 2 的2次方,也就是小数点往后移两位。
当然,也可以先把前面二进制数1.01转换成十进制1. 25,然后再乘以 2 的二次方,也就是乘以4,相乘之后得到的结果就应该是-5. 0。
因此,我们刚开始给出的单精度浮点数,它所对应的真值应该是十进制的- 5. 0。
(4)范围
1.单精度
接下来我们再来探讨这样的一个问题,对于单精度浮点型来,我们所能表示的最小绝对值和最大绝对值应该是多少?
<1> 最小绝对值
首先需要注意到尾数部分,我们说过它的最高位隐含了一个1,所以尾数部分我们要取绝对值最小,应该是取全0,而尾数全0,它实际上所对应的真实的值应该是二进制的 1. 0。
高位的 1 是原本就隐含的,这是尾数所能表示的最小的绝对值。
接下来我们要让整体的绝对值最小,需要让阶码它的真值也最小。之前我们说过,虽然阶码是用移码表示,理论上我们可以表示- 128 到+127 这样的一个范围的数。
但是-128和+127 对应的全 1 和全 0 我们会用来作为一个特殊用途。因此阶码的真值可以表示的最小值就应该到了-126
。
-126它所对应的移码机器数应该是这样的一个值:
1. 0
这样的二进制数乘以 2 的-126 次方,就意味着我们需要把小数点往前移 126 位,所以这就是单精度浮点数,它所能表示的最小的绝对值。
<2> 最大绝对值
接下来再来看最大的绝对值。
显然,我们需要让尾数的绝对值也最大,阶码的真值也达到最大。
所以我们应该让尾数全为1,再乘以 2 的 127 次方。这是阶码所能表示的最大的正值。
也就是在1.11...1的基础上,小数点往后移 127 位。
这就是单精度浮点数所能表示的最大的绝对值。
2.双精度
对于双精度浮点数,它的最小绝对值和最大绝对值也可以用一样的方法来分析,只不过是尾数还有阶码所能表示的最大值和最小值发生的一些改变而已,原理都是类似的,所以这就不再赘述。
(5)阶码特殊用途
既然单精度浮点数,它所能表示的最小绝对值只能到这样的一个数,如下:
接下来我们来思考这样的一个问题。
如果现在我们要表示的数值,它的绝对值比我们这儿推出的绝对值还要更小,怎么办?有没有可能表示比绝对值还要更小的数?其实是有可能的。
在这个地方,我们就会用到之前提到的阶码全 1 或者阶码全 0 这样的两个状态。
1.阶码全0
<1> 尾数不全为0
对于单精度浮点数
来说,阶码总共占 8 个比特。如果把它看作是无符号数,那么它所能表示的范围应该是 0- 255 。
0 这个无符号数对应的刚好就是全 0 的二进制, 255 所对应的是全1。
所以刨除全 0 和全 1 这两个状态,当阶码部分,它的值小于等于254,大于等于1的时候,才是我们之前所探讨的那种正常的区间。
当然这个地方我们对阶码的解读是把它看作是无符号数,并不是它的真值落在这些区间,大家不要混淆。
当阶码不是这两种特殊状态的时候,用之前我们提到的这种方式,就可以确定单精度浮点数它的真值到底是多少。
接下来如果阶码不在这个范围,如果它是全0,在阶码为全0,并且尾数 m 不全为 0 的时候,我们表示这是一个非规格化的小数。
在之前的讲解中,我们都是默认高位隐含了一个1,但是如果阶码全为0,我们会认为小数点前边的一位就是0。
在这种情况下,浮点数它所对应的真值应该是0.M
乘以 2 的-126 次方。
如果我们要表示的数值比之前推出的最小绝对值还要更小,比如我们要表示 0. 001 乘以 2 的- 126 次方。如果要表示这个数值,我们可以这么做。
让阶码部分全部为0,尾数部分存入的应该是001...00。最高位的 0 是隐含的。
另外,由于这个数它是一个正数,因此数符这个位应该存0,表示这是一个正数。
所以如果引入了阶码全为 0 这样的特殊状态,我们就可以用单精度浮点数来表示比我们之前提出的这些绝对值还要更小的一些数。
在这种规定下,小数的最高位它不是1,不是一个有效值,所以这种小数就是非规格化的小数。而之前我们提到的普通的情况,最高位都是1,都是规格化的小数。
这是一种比较特殊的约定。
需要注意,当解码E全为 0 的时候,我们补充的隐含最高位就变成了0。
另外,乘以 2 的多少次方,这个地方会固定为-126 次方。
虽然说在偏置值等于 127 的情况下,移码全0状态,事实上是对应-127 次方,这是我们之前推出的一个结论,但是这个地方我们会固定的把它视为乘以 2 的-126,而不是127。
所以在这种特殊状态下,我们确定阶码的真值的时候,就不是用之前提到的那种方式来进行转换的,这就是一个死的规定。
<2> 尾数全为0
再看下一种特殊的情况,如果阶码全为0,并且尾数也全为0,在这种情况下就是用于表示真值的0。
当然,浮点数里边真值 0 也可以有正 0 和负 0 这样的两种状态。具体是正 0 还是负0,其实就是看数符位它到底为 0 还是为 1 。
总之,如果用浮点数表示真值0,阶码和尾数部分都是0。
这是阶码全 0 的时候的特殊用途。
2.阶码全1
接下来再看阶码全 1 的时候,如果阶码全为1,也就是对应无符号数的 255 状态。
<1> 尾数全为0
此时如果尾数 m 全为0,我们会用这种状态来表示无穷大,有可能是正的无穷大,有可能是负的无穷大。到底是正是负,同样的也是看数符部分。
当我们对两个浮点数进行加法或者乘法之类的运算的时候,如果发生了正上溢或者负上溢,机器通常会把计算的结果记录为正无穷大或者负无穷大。有了这样的记录,接下来就可以方便我们对这种异常状况进行处理。
<2> 尾数不全为0
再来看如果解码全1,而尾数不全为0,这种情况是表示一个非数值数 NAN。
其实这是一个英文缩写,指的是 not a number 。
什么时候会用到它呢?如果我们非法的进行了比如 0/ 0 或者无穷减无穷,进行了这种非法的操作之后,这种运算在数学上是非法的,所以如果进行了这种非法运算,计算机会把非法运算的结果用一个浮点数NAN 这个类型的浮点数来记录。
同样的,有了这样的记录之后,才可以更方便我们对这种异常情况进行后续的处理。
所以,当阶码全 1 或者全0,对应无符号数 255 和 0 这两种状态的时候,我们对浮点数的解读规则是会有一些特殊规定的。
四、总结回顾
这一小节中,我们介绍了 IEEE (i triple e) 754 标准所规定的短浮点数、长浮点数,还有临时浮点数它们的基本结构。
我们举的例子主要是基于短浮点数的。长浮点数还有临时浮点数,它们的分析方法也是一样的,只不过是阶码尾数这些数值部分,它们的比特位发生了一些变化而已。
无论是哪种类型的浮点数,我们都需要记住尾数部分是用原码表示的。同时我们会规定,在正常情况下,在高位是隐含了一个 1 的,也就是需要在尾数的前面加一个小数点,再加一个1,这才是尾数真实的值。即:1.M
另外,阶码部分采用移码表示,只不过这个地方的移码它的偏置值和我们之前熟悉的 2 的 n 减 1 次方不一样。
在这个标准当中,偏置值都是 2 的 n 次方减1,还要再减一个1。
只有我们记住了偏置值是多少,我们才可以确定一个阶码的真值到底是多少。确定的方法就是把移码看作一个无符号数,先把它转换成十进制,再减掉偏置值。这样一相减,我们就可以确定阶码的真值到底是多少。
确定了阶码的真值,在尾数前边补了一个1,最后再结合数符位所反映出来的正负性,我们就可以确定整个浮点数的真值是多少。
再次强调,阶码全 1 和阶码全 0 的时候会用作一些特殊的用途。
考试的时候,如果让大家对浮点数的真值进行转换,一般来说不会给你阶码全 1 和阶码全 0 的情况。所以重点还是要掌握我们讨论的这种普通情况如何转换。