黄小华的个人网站
熬过无人问津的日子才有诗和远方!
计算机组成原理

数在计算机中的表示形式统称为机器数。计算机中处理数据及运算都是采用二进制,通常规定机器数用八位二进制表示。
实用的数据有正数和负数,因为计算机只能表示0、1两种状态,数据的正号“+”或负号“-”,在计算机里就用一位二进制
的0或1来区别,通常放在最高位,成为符号位。 符号位数值化之后,为能方便的对机器数进行算术运算、提高运算速度,
计算机设计了多种符号位与数值一起编码的方法,最常用的机器数表示方法有:原码、反码、补码和移码,
下面就分别介绍一下它们的表示方法。

一、原码、反码、补码

三种表示法的转换过程如下:

20141024112049570.png 说明:

正数原码补码反码都相同

特别的,在原码中0有两种表示方式:[+0]原=0000000,[-0]原=1000000。

在反码表示中,0也有两种表示形式:[+0]反=0000000,[-0]反=11111111。

在补码表示中,0有唯一的编码:[+0]补=0000000,[-0]补=0000000。

二、移码

因为个人移码理解有点困难,so单独解释一下:移码表示法是在数X上增加一个偏移量来定义的,
常用来表示浮点数中的阶码,所以是整数。如果机器字长为n,规定偏移量为2^(n-1)。
若X是整数,则[X]移=2^(n-1)+X
则[+45]=+0101101+10000000, [-45]=-0101101+10000000=01010011

 实际上由此可推出,在偏移2^(n-1)的情况下,只要将补码的符号位取反便可获得相应的移码表示。

20141024112100522.png

说明:在移码表示中,0也编码是相同的,[+0]移=1000000,[-0]移=1000000。 计算机之所以这些编码方法是为了便于运算,提高运算速度。四种表示方法其实是层层递进的,即会求十进制的二进制表示,
记住符号位的正负表示,知道怎么递进的它们之间的关系。

浮点数加减例题

QQ截图20191220204711.png
码的乘除运算 QQ截图20191220204650.png
QQ截图20191220204608.png

浮点数

浮点数运算是一个非常有技术含量的话题,不太容易掌握。许多程序员都不清楚使用==操作符比较float/double类型的话到底出现什么问题。
许多人使用float/double进行货币计算时经常会犯错.

例题 0.1 + 0.2 = ?

在浏览器中测试下计算结果,得到的结果是 0.30000000000000004,并不是理想中的 0.3 结果值。
为什么会存在这样的误差呢?这是数值运算会存在精度丢失的问题
十进制的 0.1 和 0.2 转换成二进制数值都会是无限循环的值

0.1 -> 0.0001100110011001...(无限)
0.2 -> 0.0011001100110011...(无限)
而根据 IEEE754 标准,尾数最多能存储53位有效数字,那么就必须在特定的位置进行四舍五入处理,
得到的结果分别是:
0.1 -> 0.0001100110011001100110011001100110011001100110011001101
0.2 -> 0.001100110011001100110011001100110011001100110011001101
所以,相加得到的二进制结果为:
0.0001100110011001100110011001100110011001100110011001101 +
0.001100110011001100110011001100110011001100110011001101
= 0.0100110011001100110011001100110011001100110011001100111
二进制结果转换成十进制就是 0.30000000000000004。

同样的数值可以有多种浮点数表达方式,比如 123.45 可以表达为 12.345 × 10^2,0.12345 × 10^3 或者 1.2345 × 10^2。
因为这种多样性,有必要对其加以规范化以达到统一表达的目标。规范的(Normalized)浮点数表达方式具有如下形式:

  d.dd...d × β^e , (0 ≤ di < β)

  其中 d.dd...d 即尾数,β 为基数,e 为指数。尾数中数字的个数称为精度,在本文中用 p(presion) 来表示。
每个数字 d 介于 0 和基数β之间,包括 0。小数点左侧的数字不为 0

浮点数加减运算(总共包括5个步骤):

1、对阶

对阶就是比较参与运算的浮点数的阶码大小,然后使他们的阶码(或移码)都一样,其最终目的是使参与运算的数的小数点位置对齐,以便得出正确的结果。 既然需要使参与运算的浮点数的阶码一样,这时就要以某一个浮点数的阶码为标准了,通常以大阶码为准,即阶码小的数,要向阶码大的数对齐,也就是将小阶码的值调成与大阶码的值一样,这时,为了确保小阶码的数值与原来的保持一致,肯定需要对小阶码的尾数的小数点进行移位,就像十进制数中的1.2345 * 103,如果将指数3变成5,那么尾数1.2345的小数点位置不可能保持不变,要向左移两位,变成0.012345,所以,最终的形式为0.012345 * 105。 因为是以大阶码为准,所以小阶码需要增大,那么尾数需要做相同倍数的减小,才能保持数值不变(即阶码小的尾数需要向右移对应的位数[小数点是向左移]),因为原则是,在尾数最高位前面加对应数量的0,然后原尾数最后的对应尾数直接丢弃,移了多少位就丢弃多少位(直接丢弃是因为存储尾数的存储器空间是固定的)。

对阶中的尾数移位可以借助十进制的科学计数法来帮助理解,假设十进制数1234.5原来表示成1.2345 * 103,现在需要表示成106的格式,则对应的格式为0.0012345 * 106(在原来的小数点右边最高位前面移了三位,补加了两个0),又假设小数点后面最多只能4位(浮点数中用来保存尾数的存储空间是固定的),这样一来,最终的存储格式是0.0012 * 106,尾数中原来的"345"就被丢弃了,虽然这会在一定程度上导致与原值有区别,但还是在最大限度上保持了不变。

示例:X = 20010 * 0.11000101,Y = 20100 * 0.10101110。对这两个浮点数进行加减法运算(尾数的符号位是体现在浮点数的符号位上,阶码和尾数均已采用补码形式表示)。 X的阶码为0010,对应的十进制数位2,Y的阶码为0100,对应的十进制数为4,因此,X的阶码比Y的阶码小,此时应将X的阶码变成与Y的阶码一样,即都调成4,同时需要将X的尾数小数点左移2位(在小数点右边最高位前面加两个0,原来最低的2位被丢弃,这样才能使X的值与原来保持基本不变)。 X原来的尾数为11000101,向右移两位(仍要保持原来的总位数不变,则要在前面补相应位数的0,原来最右边的对应两位丢弃),则变成了00110001,这样一来,X的阶码也变成了0100,最终X在存储器中的格式是0 0100 00110001(原来为0 0010 11000101)。

2、尾数运算

通过对阶将尾数的小数点对齐之后,就能将经过移位的尾数进行加减运算,这一步很简单,可以直接参照博文–“无符号二进制运算”。 继续前面的示例:X = 20010 * 0.11000101,Y = 20100 * 0.10101110,现要求X+Y。 X对阶后的尾数为00110001,Y的尾数不变,为10101110,两者相加:

X 00110001 Y 10101110 结果 11011111

3、规格化处理

规格化处理主要是针对浮点数的尾数部分,规格化的尾数格式要求如下: 尾数采用原码表示形式时:正数的规格化格式为:0.1XXX,负数的规格化形式为1.1XXX。 尾数采用补码表示形式时,正数的规格化格式为:0.1XXX,负数的规格化形式为:1.0XXX。 以上的最高位均代表符号位(0代表正数,1代表负数),关键是看小数点后面的数的格式。

对于双符号位(用两位来表示符号)的补码形式尾数,正数的规格化格式为00.1XXX,负数的规格化格式为:11.0XXX,以上最高两位代表符号位(00代表正数,11代表负数),关键是看小数点后面数的格式。 引入双符号位的设计目的就是为了能快速检测出运算结果是否有溢出,因为双符号位规定了"00代表正数,11代表负数",如果最高两位不是这两种形式,而是"01"或者是"10"就能快速知道有溢出了,符号位为"01"的时候称为"上溢",即最高真值位相加后有进1,为"10"时,称为"下溢",即最高真值位相减后有借1。 —引自王达老师《深入理解计算机网络》一书

本文内容仅涉及单符号位,有兴趣了解双符号位的,可以关注后续博文。 凡不符合以上格式要求的尾数均要进行规格化,对以上规格化格式进行总结可以得出:符号位与尾数最高位不一致才算是规格化,一致为非规格化。如1.0XXX,0.1XXX(最前面的为符号位)之类的尾数都是规格化的数,而1.1XXX,0.0XXX为非规格化的数,另外,如果尾数大余1,也是非规格化数。 对于非规格化的尾数需要进行相应的处理,处理方式又分为"左规"和"右规",所谓"左规"就是尾数需要向左移位,每移1位阶码值减1,直到为规格化数为止,对应"右规"就是尾数要向右移位,每移1位阶码值减1,直到为规格化数为止。 采用"左规"还是"右规"的基本原则如下: 运算结果产生移益出(由于原来两尾数的最高有效位相加有进位或者相减有借位时形成的)时,必须进行"右规",如双符号位的运算结果为10.XXX或01.XXX格式就是不符合规格化要求了,因为双符号位时符号位为"10"和"01"都是不正确的,右规时,最高有效位前补相应数量的0,此时,10.XXX格式右规后的格式为11.0XXX,而01.XXX格式右规后的格式为00.1XXX。 如果运算结果出现0.1XXX或者1.1XXX(即符号位与尾数最高有效位相同时),必须进行左规,左规时最低有效位后补相应位数的0。 在上面的例子中,X与Y的尾数之和是11011111,又因为它是正数,所以可以表示成0.11011111,已经是规格化,不用再处理了。 如果X与Y相减,得出的尾数之差为负数,如1.1101111,这时就要进行规格化处理了,因为符号位1与尾数最高位1相同,这时需要进行左规,即向左移位,11011111要向左移两位才能使尾数的最高位与符号位不一致,移位后的尾数为01111100(最后两个0是补上去的)同时阶码要从原值0100相应减2,得到0010。 QQ截图20191220204748.png

4、舍入处理

在"对阶"和向左,向右规格化处理时,尾数要向左,向右移位,这样结果尾数与原先的尾数就会有一定的误差,因此要进行舍入处理,以尽可能减小这种误差。 以下提供两种减小误差的方法: “0舍1入法”: 类似十进制中的四舍五入法,即如果左规或右规时丢弃的是0,则舍去不计,反之要将尾数的末尾加1。 如上面举例的X和Y的尾数之和为:1.11011111(最高位为符号位),需要左规,得到1.01111100,由此可见左移时去掉的是前面的两位1,这种情况下就需要在尾数的最后一位加1,这样进行舍入处理的结果就是:1.01111101。 ‘‘恒置1法’’: 只要有数位被左规或者右规丢弃掉,就要在尾数的末尾恒置1,但是这个方法精确度不高,因为从概率上来讲,丢弃0和1的概率各占50%。 IEEE 754还有许多更复杂的舍入模式,有兴趣可以自行了解。

5、溢出处理

如果采用双符号位补码形式,出现了"01"或者是"10"的形式就表示有溢出,但是,如果采用的是单符号位就不好判定了。这时需要根据对尾数进行规格化处理后的阶码是否超出了当前阶码所能表示的取值范围来判定了。 上文已经提到了,右规后阶码需要加上对应的值,尾数每向右移一位就加1,最终可能导致阶码超出了当前类型的阶码所能表示的数值范围,这称为"上溢"。 如(X+Y)补浮点数经过"对阶"和尾数相加后得到的尾数为10.101110011,显然它需要右规,结果为11.01110011,相当于尾数右移了一位,原来尾数的最高有效位"1"被丢弃了,同时阶码也需要相应加1,假设原先是用4位来保存阶码(算上阶码的符号位,所以,最大能表示的数是+7),原阶码为0111,尾数右移一位后,阶码+1就变为8了,而这个是大于+7的,所以这是就需要进行溢出处理了。 同时,尾数进行左规的时候,也有溢出的情况,因为左规后可能超出了用于存储阶码的存储位所能表示的最小值(称为"下溢"),如左规后得到的新阶码为-5,假设原先的阶码是用4为来存储的,现在要求尾数再左移3位,这样阶码也要减小3,得到-8,而-8也超出了4位阶码所能表示的最小值-7(阶码的最高位为符号位)。如果发现有溢出,计算机会根据以下原则进行处理: 如果是上溢,立即停止运算,做中断处理。 如果是下溢,整个尾数按0处理。

浮点数