Java架构师成长之道之计算机组成原理计算篇
3.1 进制运算基础
3.1.1 进制的概述
进制的定义:进制是一种计数方式,也称为进位计数法或者位值计数法,使用有限数字符号表示无限的数值,使用的数字符号的数目称为这种进位制的基数或者底数,例如十进制就是由0-9十个数字组成。
在计算机内存中,都是以二进制的补码形式来存储数据的,生活中以十进制方式计算的数据居多,例如账户余额,开发人员的薪水等等。计算的内存地址、MAC地址等等通常都是使用十六进制表示的,Linux系统的权限系统采用八进制的数据表示的。
相同进制类型数据进行运算时会遵守加法:逢R进1;减法:借1当R,其中R就表示进制。
如下表格是计算机常用进制的组成、示例和使用场景:
进制名称 | 组成 | 数值示例 | 典型使用场景 |
---|---|---|---|
二进制 | 0,1 | 101 | 内存数据存储 |
八进制 | 0-7之间的8个整数 | 012(以0开头) | Linux权限 |
十进制 | 0-9之间的10个整数 | 12 | 整数 |
十六进制 | 0-9,a-f之间的10个数字加6个字母 | 12f | 数据的内存地址 |
3.1.2 计算机底层为什么只能识别二进制
我们目前主要使用的计算机都是大规模集成电路机,是采用大规模和超大规模的集成电路作为逻辑元件的。
集成电路,按其功能、结构的不同,可以分为模拟集成电路、数字集成电路和数/模混合集成电路三大类。而我们的计算机主要是采用数字集成电路搭建的。
逻辑门是数字逻辑电路的基本单元。常见的逻辑门包括“与”门,“或”门,“非”门,“异或”等等。通过逻辑门可以组合使用实现更为复杂的逻辑运算和数值运算。
逻辑门可以通过控制高、低电平,从而实现逻辑运算。电源电压大小的波动对其没有影响,温度和工艺偏差对其工作的可靠性影响也比模拟电路小得多,所以相对稳定。
因为数字计算机是由逻辑门组成,而逻辑电路最基础的状态就是两个——开和关。所以,数字电路是以二进制逻辑代数为数学基础。二进制的基本运算规则简单,运算操作方便,这样一来有利于简化计算机内部结构,提高运算速度。
但是在日常开发中,通常都会使用八进制和十六进制,因为八进制和十六进制相对于二进制表示数据更加简洁,而且一个八进制表示三个二进制,一个十六进制表示四个二进制。
例如 1024使用二进制表示为0b100 0000 0000,使用八进制表示为0o2000,使用十六进制表示为0x400。
3.1.3 二进制运算的基础
首先明确不同进制的值是如何计算的,这里以十进制和二进制为例子,阐述它们的计算过程。
十进制1024
1024=1*10^3+2*10^1+4*10^0=1000+20+4=1024
二进制的0b10000000000
0b10000000000 =1*2^10=1024
3.2 进制之间的转换
3.2.1 二进制转十进制(整数)
二进制整数转十进制整数是使用按权展开法计算的,这里以二进制数据01100101为例子。
从右往左开始数,如果二进制位为1,则依次用1*2^n,n从0开始。
01100101 转换为十进制的计算过程如下所示
01100101=1*2^6+1*2^5+1*2^2+1*2^0=64+32+4+1=101
3.2.2 十进制转二进制(整数)
十进制整数转二进制整数是采用重复相除法来实现的,具体的实现就是将十进制的整数除以2,求余数,直到商数为0,然后将余数倒转的结果就是十进制转二进制的结果。
以十进制101为例子,转换为二进制的计算过程如下表格所示
重复除以2 | 商数 | 余数 |
---|---|---|
101/2 | 50 | 1 |
50/2 | 25 | 0 |
25/2 | 12 | 1 |
12/2 | 6 | 0 |
6/2 | 3 | 0 |
3/2 | 1 | 1 |
1/2 | 0 | 1 |
然后将余数的结果从下到上串联起来的结果:1100101,即十进制的101转换为二进制的结果为1100101
再来一个例子:将十进制的237转换为二进制
重复除以2 | 商数 | 余数 |
---|---|---|
237/2 | 118 | 1 |
118/2 | 59 | 0 |
59/2 | 29 | 1 |
29/2 | 14 | 1 |
14/2 | 7 | 0 |
7/2 | 3 | 1 |
3/2 | 1 | 1 |
1/2 | 0 | 1 |
然后将余数的结果从下到上串联起来的结果:11101101,即十进制的237转换为二进制的结果为11101101。
3.2.3 二进制转十进制(小数)
二进制的小数转换为十进制的小数也是使用按权展开法计算的,只不过首先是从左向右数,从2-1依次递减开始计算的,2-1就是除以2,2^-2就是除以4,依次类推。
例如小数0.11001转换为十进制的计算过程如下所示。
0.11001=1*2^-1+1*2^-2+1*2^-5=0.5+0.25+1/32=0.75+0.03125=0.78125=25/32
再将一个二进制的小数转换为十进制
0.01011=1*2^-2 +1*2^-4 +1*2^-5 =0.25+0.0625+0.03125=0.34375=11/32
3.2.4 十进制转二进制(小数)
十进制的小数转换为二进制的小数使用重复相乘法来计算的。
下面以一个小数0.78125为例子,介绍转换二进制的过程。
0.78125转换为分数的形式就是25/32
重复乘以2 | 积 | 取1 |
---|---|---|
25/32 | 50/32=1+9/16 | 1 |
9/16 | 18/16=1+1/8 | 1 |
1/8 | 2/8=0+1/4 | 0 |
1/4 | 2/4=0+1/2 | 0 |
1/2 | 2/2=1+0 | 1 |
然后再将取1的结果从上到下串联起来,即0.78125转换为二进制的结果是0.11001
再计算一个小数0.34375,转换为分数形式为11/32,计算步骤如下所示
重复乘以2 | 积 | 取1 |
---|---|---|
11/32 | 22/32=0+11/16 | 0 |
11/16 | 22/16=1+3/8 | 1 |
3/8 | 6/8=0+3/4 | 0 |
3/4 | 6/4=1+1/2 | 1 |
1/2 | 2/2=1+0 | 1 |
然后再将取1的结果从上到下串联起来,即0.34375转换为二进制的结果为0.01011。
3.3 二进制数据的表示方法
3.3.1 有符号数和无符号数
在十进制中使用加号(+)表示整数,使用减号(-)表示负数,那么负数在计算机中是如何表示的呢?
首先以正整数127(占1个字节)为例子,转换为二进制的结果是01111111,那么-127如何表示呢,结果是11111111,因为计算机在存储数据时,按照符号位和数字位组成,其中符号位是二进制数据的左边第一位数字,如果是0,则表示该数字是整数,例如01111111,如果是1表示该数字为负数,例如11111111,这样的表示方法就是原码表示法,原码表示法在表示数据时有如下特点:
- 使用0表示正数,使用1表示负数
- 规定数值的最高位是符号位
- 表达简单明了,是人类最容易理解的方法
前面在进行进制转换的使用使用的按权展开法,重复相乘法,重复相除法都是基于二进制原码表示法进行操作的。
但是原码表示法有有些缺陷,比如说在使用原码表示0的时候,这里以一个字节为例:0的正数表示为00000000,0的负数是10000000,不管正0和负0其实都是0,容易产生歧义。
原码表示法在进行数据计算的时候特别复杂,特别是两个操作数符号位不相同的时候,主要需要以下步骤:
- 判断两个操作数的大小
- 使用绝对值大的数字减去绝对值小的数字
- 对于符号值,以绝对值大的为准
3.3.2 二进制的补码表示法
引进补码的目的:
- 减法运算复杂,希望找到正数代替负数的方法
- 使用加法运算代替减法运算,从而消除减法。
补码的计算方式公式如下所示
x>0, x=x
x<0,x=2^(n+1)+x
其中n表示二进制的位数,以一个字节为例,那么n就是8,如果是两个字节,那么n就是16,依次类推。
补码的计算案例
- 利用补码的计算公式计算一个正数例子:假设n=8,x=13,计算x的原码和补码
13的原码以一个字节表示就是00001101。
而13大于0,因此13的补码和原码一样,也就是00001101。
- 利用补码的计算公式计算一个负数的例子:假设n=8,x=-13,计算x的原码和补码
-13的原码为10001101,因为是负数,所以最高位为-1
而-13的补码可以根据公式x=2^n+1+x
x=2^(8+1)+ -13=512-13=499=1 1111 0011
而 1 1111 0011已经超出了一个字节表示的数据范围(-128-127),所以需要将最左边的1截取掉,因此-13的补码结果为1111 0011。
- 利用补码的计算公式计算负数的补码:假设n=8,x=-7,计算x的原码和补码
-7的原码为 10000111
-7的补码可以根据公式x=2^(n+1)+x
x=2^(n+1)+x=2^9+ -7=512-7=505=1 1111 1001
同样的道理,1 1111 1001超过了一个字节的表示范围(-128-127),所以需要将最左边的1截取掉,即-7的补码表示为11111001。
- 利用补码的公式计算负数的补码:假设n=8,x=-1,计算x的原码和补码
-1的原码为10000001
-1的补码可以根据公式x=2^(n+1)+x
x=2^(8+1)+ -1=512-1=511=1 1111 1111
同样的道理,1 1111 1111超过了一个字节的表示范围(-128-127),所以需要将最左边的1截取掉,即-1的补码表示为11111111。
以上的例子在计算补码的过程中还是使用了减法实现计算。
3.3.3 二进制的反码表示法
反码出现的目的就是找出原码和补码的规律,消除转换过程中的减法。
反码的计算公式如下所示
x>0, x=x
x<0,x=(2^(n+1)-1)+x
反码计算案例
- x=-13,n=8,计算x的原码和反码
-13的原码是1000 1101
-13的反码可以根据公式x=(2^(n+1)-1)+x
x=(2^(8+1)-1)+ -13=511-13=498=1 11110010
由于1 11110010超过了一个字节的表示范围(-128-127),所以需要将最左边的1截取掉,即-13的补码表示为11110010。
2.x=-7,n=8,计算x的原码和反码
-7的原码为10000111
-7的反码可以根据公式x=(2^(n+1)-1)+x
x=(2^(8+1)-1)+ -7=511-7=504=1 11111000
由于1 11111000超过了一个字节的表示范围(-128-127),所以需要将最左边的1截取掉,即-7的反码表示为1111000。
这里采用一个字节存储,总结下之前使用过的十进制整数7,-7和13,-13的原码、补码、反码
十进制 | 原码 | 补码 | 反码 |
---|---|---|---|
7 | 7 | 7 | 7 |
-7 | 10000111 | 11111001 | 1111000 |
13 | 13 | 13 | 13 |
-13 | 1000 1101 | 1111 0011 | 11110010 |
根据以上表格的数据可以获取到以下结论
- 整数的原码、反码和补码都是一样的
- 负数的原码求反码,最高位不变,其他位取反
- 负数的反码求补码,反码加1
根据规律计算原码、反码和补码的案例
1.x=-7,求原码、反码和补码
-7的原码是1000 0111,反码是1111 1000,补码是1111 1001
2.x=-9,求x的原码、反码补码
-9的原码是100001001,反码是111110110,补码是11110111
原码、补码、反码总结:
由于原码存在着表示0有歧义,减法运算复杂的问题,因此引进了补码解决了这俩问题,但是在原码转换为反码的计算过程中还是使用了减法,因此引进了反码则是消除了原码计算补码时需要使用减法,原码转换为反码只需要最高位不变,其他位取反即可,而反码转换为补码只需要在反码的基础上加1即可,源码计算反码通过先求反码,再求补码消除了减法。
3.3.4 小数的二进制补码表示法
首先回顾下整数的二进制补码表示法的计算公式
x>0, x=x
x<0,x=2^(n+1)+x
而小数的二进制补码表示法的计算公式如下所示
x>0, x=x
x<0,x=2+x
小数的二进制补码计算案例
- x=9/16,计算x的原码,反码和补码
首先将十进制小数9/16转换为二进制,计算流程如下
重复乘以2 | 积 | 取1 |
---|---|---|
9/16 | 18/16=1+1/8 | 1 |
1/8 | 2/8=0+1/4 | 1 |
1/4 | 2/4=0+1/2 | 0 |
1/2 | 2/2=1+0 | 1 |
即9/16的二进制转换结果为0,0.1101,默认的转换结果为原码表示,因为9/16是正数,因此其二进制的原码、反码和补码都是0,0.1101。
- x=-(11/32),计算x的原码、反码和补码
首先将十进制小数数-(11/32)转换为二进制,计算流程如下
重复乘以2 | 积 | 取1 |
---|---|---|
11/32 | 22/32=0+11/16 | 0 |
11/16 | 22/16=1+3/8 | 1 |
3/8 | 6/8=0+3/4 | 0 |
3/4 | 6/4=1+1/2 | 1 |
1/2 | 2/2=1+0 | 1 |
即-(11/32)的二进制转换结果为1,0.01011,默认的转换结果为原码表示,还需要求x的反码和补码,x的反码是1,1.10100,x的补码是1,1.10101。
3.4 二进制数据的运算
3.4.1 定点数与浮点数
定点数的表示方法:定点数就是小数的固定在某个位置的数。
定点数有两种表示方法,第一种是小数点在符号位和数值位数的中间,这种被称为纯小数,如下表格所示
数值 | 符号位 | 数值位 |
---|---|---|
0.1011 | 0 | 1011 |
-0.1011 | 1 | 1011 |
第二种是小数点在数值位的最后面,这种被称为纯整数,如下表格所示
数值 | 符号位 | 数值位 |
---|---|---|
1011 | 0 | 1011 |
1011 | 1 | 1011 |
如果想要表示的数据不是纯整数也不是纯小数,此时必须乘以比例因子以满足定点数保存格式,例如二进制10.01可以通过将小数点左边移两位满足纯小数的表达方法,或者通过将小数点右边移动两位满足纯整数的表达方法。
3.4.2 浮点数的表示方法
首先明确浮点数的由来,因为计算机经常处理的数据绝大部分都不是纯小数或者纯整数,而且当数据的范围很大,难以用定点数表示,并且定点数不够灵活,因此出现了浮点数。
浮点数的表示形式
为了了解浮点数的表现形式,首先回一下学校学过的科学计数法。
例如整数123450000使用科学计数法表示为1.2345*10^8
,其中1.2345表示尾数,10表示基数,8表示的是阶码,在浮点数的表示格式中也存在尾数、基数和阶码的概念。
对于任意的浮点数都可以使用如下公式转换
N=S*r^j
其中S表示尾数,r表示基数,j表示阶码。
例如二进制小数 11.0101=0.110101210,需要指出的是210的10指的是2的二进制表示方式,并不是十进制的10
11.0101还可以表示为0.0110101211,需要指出的是211的11指的是3的二进制表示方式,并不是十进制的11。
3.4.3 浮点数在计算机中的存储
在计算机内部,浮点数的存储包含四部分,分别是 阶码符号位、阶码数值位、尾数符号位、尾数数值位,因为阶码和尾数可能是负数,所以有符号位,而且浮点数的尾数必须是纯小数。
例如二进制的11.0101按照不同的阶码在内存的存储方式为
11.0101=0.1101012^10 阶码为二进制的10
11.0101=0.01101012^11 阶码为二进制的11
二进制浮点数数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位(8位) |
---|---|---|---|---|
0.110101*2^10 | 0 | 10 | 0 | 11010100 |
0.0110101*2^11 | 0 | 11 | 0 | 01101010 |
由于这里的尾数数值必须满足8位,如果不足8位,需要用0补足。
3.4.4 浮点数的表示范围
由于浮点数是由阶码和尾数组成,因此考虑浮点数的表示范围实际上就是阶码和尾数的取值范围。
这里方便理解假设阶码的数值有m位,尾数的数值有n位,对于任意一个浮点数,阶码表示的最大值就是2^m-1
,假设阶码有8位并且全是1,那么这个阶码表示的最大值就是255,如果考虑符号位,那么阶码的取值范围就是-(2^m-1) - 2^m-1
,那么8位数阶码的取值范围就是-255 - 255
。
尾数要求是纯小数,尾数能表示的最大值1-2^-n
,尾数所能表示的最小值2^-n
,因此尾数的表示范围是2^-n - 1-2^-n
,如果考虑符号位,尾数的取值范围是
负数:-(1-2^-n) - -(2^-n)
正数: 2^-n - 1-2^-n
所以浮点数表示的最小负数值是-(1-2^-n)*2^(2^m-1)
,最大的负数是-(2^-n)*2^-(2^m-1)
。
浮点数表示的最大的正数值是(1-2^-n)*2^(2^m-1)
,最小的正数值是(2^-n)*2^-(2^m-1)
。
如果超过了浮点数表示的最大值则会发生数据溢出,称为上溢(数值太大)。
如果超过了浮点数表示的最小值则会发生数据溢出,称为下溢(数值太小)。
日常开发中经常使用单精度和双精度浮点数,其中单精度是使用4个字节即32位存储的浮点数
而双精度是使用8个字节即64位存储的浮点数。
3.4.5 浮点数的规格化
首先回到之前的科学计数法的例子:123450000=1.2345*10^8,
科学计数法规定了尾数必须在1-10之间,例如12345 000 0000=12.345*10^10
,12.345超过了尾数的范围则会不符合要求。
同样在浮点数中的尾数也有相关的要求
- 尾数必须是纯小数
- 尾数最高位必须是1
符合浮点数规格化的表示方法案例
11.0101=0.110101*2^10
尾数的最高位不是1,不符合规范
11.0101=0.0110101*2^11
尾数的最高位不是1,不符合规范
11.0101=0.00110101*2^100
尾数不是纯小数,不符合规范
11.0101=1.10101*2^1
浮点数阶码与尾数的案例
1.假设浮点数字长16位,尾数为11位,阶码为5位,将十进制数13/128表示为二进制的浮点数
首先明确这里的浮点数由1位尾数符号位加上10位尾数数值加上1位阶码符号位加上4位阶码数值组成。
然后将十进制数13/128转换为浮点数,计算过程如下所示
重复乘以2 | 积 | 取1 |
---|---|---|
13/128 | 26/128=0+13/64 | 0 |
13/64 | 26/64=0+13/32 | 0 |
13/32 | 26/32=0+13/16 | 0 |
13/16 | 26/16=1+5/8 | 1 |
5/8 | 10/8=1+1/4 | 1 |
1/4 | 2/4=0+1/2 | 0 |
1/2 | 2/2=1+0 | 1 |
十进制13/128转换为二进制的原码结果是0.0 001 101 000
,由于正数的原码、反码、补码一样,这里不需要转换成补码。而后面的三个0是为了满足尾数数值为10位的要求
再按照规格化的要求转换浮点数,向左移动3位(乘以2的-3次方)即可满足尾数最高位必须是1
0.0001101000=0.1101000*2^-11,其在计算机的存储格式如下表格所示
数值 | 阶码数值 | 阶码符号 | 尾数数值 | 尾数符号 |
---|---|---|---|---|
0.1101000*2^-11 | 0011 | 1 | 1101000000 | 0 |
2.假设浮点数字长16位,尾数为11位,阶码为5位,将十进制数-54表示为二进制的浮点数
首先明确这里的浮点数由1位尾数符号位加上10位尾数数值加上1位阶码符号位加上4位阶码数值组成。
然后将-54转换为二进制,-54的原码表示方式为1,11 0110,逗号左边的1表示为负数,然后再将110110按照浮点数规格化的要求转换110110=0.110110*2^110
然后再按照16位浮点的字长转换
首先是尾数为10位,即110110转换成110110 0000,转换之后的反码为001001 1111,尾数的补码是00101 0000 ,,由于尾数补码的最高位是0,因此尾数符号的结果就是1。
尾数确定之后就可以计算阶码,阶码符号是0,因为阶码是正数,而阶码数值是0110,因为要满足接码数值的位数是4位,最后就可以计算出来-54在计算机中是如何存储的了
数值 | 阶码数值 | 阶码符号 | 尾数数值 | 尾数符号 |
---|---|---|---|---|
0.110110*2^110 | 0110 | 1 | 00101 0000 | 0 |
需要注意的是,计算机的数据最终是以补码的方式存储的。,因此这里的尾数数值取得是补码的结果
3.4.6 定点数和浮点数的对比
- 当定点数和浮点数尾数相同时,浮点数表示的范围更大
- 当浮点数尾数为规格化数时,浮点数的精度更高
- 浮点数的运算包含阶码和尾数,浮点数的运算更加复杂
- 浮点数在数的表示范围、精度、溢出处理、编程等方面均优于定点数
- 定点数在运算规则、运算速度、硬件成本优于浮点数
- 目前绝大多数计算机都是采用浮点数运算,而定点数使用在成本比较低的芯片中
3.5 定点数和浮点数的运算
3.5.1 定点数的加法运算
定点数的加法分为整数部分和小数部分,例如两个定点数A和B相加
整数加法: A的补码+B的补码=(A+B)的补码(mod2^n+1)
小数加法: A的补码+B的补码=(A+B)的补码(mod2)
在实际运算时,数值位与符号位一同参与运算,并将符号位产生的进位自然丢掉,也就是(mod2^n+1)和(mod2)的操作。
这里先列出原码以及反码的计算公式
原码=补码取反加1
补码=原码取反加1
定点数的加法运算案例:
1.假设 A=-110010, B=001101,求A+B
首先求A -110010的补码 求反码结果是001101,求补码的结果是1,001110,其中最左边的1表示符号位。由于B是正数,因此B的补码就是B的原码,也就是001101。
符号位与数值位计算过程
A+B=A的补码+B的补码=(A+B)的补码(mod2^n+1)=1,011011=-100101
1,001110
0,001101
1,011011
1,0111011 表示为补码,还需要转换为原码,补码首先减去1求反码的结果是1,011010,反码求原码的结果是100101,即最终A+B的结果是-100101。
2.假设A=-0.1010010,B=0.0110100,求A+B的和
首先求出A的补码,1,0.1010010的反码是1,1.0101101,补码是原码末位加1,结果为1,1.0101110
由于B的最高位是0,因此B的补码是0.0110100。
符号位与数值位一同计算的过程
1,1.0101110
0,0.0110100
1,1.1100010
A+B=A的补码+B的补码=1,1.0101110+0,0.0110100=1,1.1100010
然后将补码1,1.1100010求反码的结果是1,0.0011101,再将末位加1,求原码结果为1,0.0011110,也就是A+B的最终结果是-0.00111100
3.A=-10010000,B=-01010000,求A+B的和
首先计算出A的补码,即原码取反加1的结果为1, 01110000
然后再计算忽B的补码,即原码取反加1的结果为1,10110000
然后再将符号位和数值位一同参与运算,计算过程如下
1,0111 0000
1,1011 0000
11,0010 0000
由于数值位和符号位一同运算时发生了进位,此时符号位产生的进位自然丢掉
即A+B=A的补码+B的补码=1,01110000+1,10110000=1,0010 0000
1,00100000的反码是1,11011111,然后在末位加1,即A+B的结果是-11100000
4.A=-10010000,B=-11010000,求A+B的和
首先计算A的补码1,10010000取反的结果是1,01101111,然后加1的结果是1,01110000。
然后计算B的补码1,11010000取反的结果是1,00101111,然后加1的结果是1,00110000。
即A+B=A的补码+B的补码=1,01110000+1,00110000
然后再将符号位和数值位一同参与运算,计算过程如下
1,01110000
1,00110000
10,10100000
由于数值位和符号位一同运算时发生了进位,此时符号位产生的进位自然丢掉
因此A+B=A的补码+B的补码=1,01110000+1,00110000=0,10100000,由于最高位是0,因此A+B的原码和补码一致,也就是A+B的最终结果是10100000。
负数和负数相加的结果应该是负数,但是这里的结果10100000是正数,显然结果是不正确的,这里我们将二进制的运算数据转换为十进制相加,即-144+ -208=-352,正确的运算结果的十进制值是-352。
因为这里发生了溢出现象,我们使用1个字节的数值存储了-352,超过了它的表示范围。
我们可以采用双符号位判断法来判断数据在运算的过程中是否发生了溢出现象。
所谓的双符号位判断法就是将原来用单符号位表示成双符号位,即0换成00,1换成11,而且在运算过程中,双符号位运算所产生的进位也会丢弃,而结果的双符号位不同则表示溢出,一旦发生了溢出现象也就意味着计算结果是不正确的。
双符号位判断法的案例
1.A=-10010000,B=-11010000,使用双符号位求A+B的和
首先计算A的补码1,10010000取反的结果是1,01101111,然后加1的结果是1,01110000。
然后计算B的补码1,11010000取反的结果是1,00101111,然后加1的结果是1,00110000。
即A+B=A的补码+B的补码=1,01110000+1,00110000
然后再将符号位和数值位一同参与运算,计算过程如下
11,01110000
11,00110000
1 10,10100000
由于数值位和符号位一同运算时发生了进位,此时符号位产生的进位自然丢掉
因此A+B=A的补码+B的补码=11,01110000+11,00110000=10,10100000,由于双符号位不同,表示溢出,因此计算结果是错误的。
2.A=-10010000,B=-01010000,使用双符号位求A+B的和
首先计算出A的补码,即原码取反加1的结果为11, 01110000
然后再计算忽B的补码,即原码取反加1的结果为11,10110000
然后再将符号位和数值位一同参与运算,计算过程如下
11,0111 0000
11,1011 0000
1 11,0010 0000
由于数值位和符号位一同运算时发生了进位,此时符号位产生的进位自然丢掉
即A+B=A的补码+B的补码=11,01110000+11,10110000=11,0010 0000,由于11表示双符号相同,即计算结果为没有溢出。
11,00100000的反码是11,11011111,然后在末位加1,即A+B的结果是-11100000
3.5.2 定点数的减法运算
定点数的减法运算也分为整数减法和小数减法两个部分,例如两个定点数A和B相减法有如下公式。
整数减法:A的补码-B的补码=(A+ -B)的补码(mod2^n+1)
小数减法:A的补码-B的补码=(A+ -B)的补码(mod2)
减法运算实际上还是转换成了加法运算,即减去一个数等于加上这个数的负数,而-B的补码等于 B的补码连同符号位按位取反,末位加1。
例如B的补码等于1,0010101,那么-B的补码就是0,1101011。
定点数减法运算案例
A=11001000,B=-00110100,求A-B
首先求A的补码,由于A是正数,因此A的补码是0,11001000
因为A-B= A+ -B
所以先求出B的补码,1,00110100的反码是1,11001011,然后再加1的结果是1,11001100。
然后再求出-B的补码0,00110100。
因此A-B=11001000+00110100
00,11001000
00,00110100
00,11111100
即A-B=11111100
3.5.3 浮点数的加减法运算
首先使用x,y分别表示两个浮点数,根据之前学习的浮点数表示法知道
对于任意一个浮点数,都可以使用N=S*r^j
来表示,因此x,y分别使用如下形式表示。
x=S * r^j
y=S * r^j
其中S表示尾数,r表示基数,j表示阶码。
对于一个浮点数的加减运算,会经历如下5个步骤
- 对阶
- 尾数求和
- 尾数规格化
- 舍入
- 溢出判断
- 对阶
对阶的目的就是使得两个浮点数的阶码一致,使得尾数可以进行运算,因为浮点数的尾数运算实际上就是之前学习的定点数运算,相对简单。但是浮点数位数的实际小数位阶码有关,如果阶码不对阶则无法进行浮点数的运算,而对阶的阶码则会按照小阶看齐大阶的原则。
例如二进制x=0.1101*2^01,y=(-0.1010)*2^11
,在计算机中的存储如下表格所示,其中符号位使用双位符号位存储,而数值位使用4位存储。
对阶之前的存储
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 | 00 01 | 00 | 1101 |
y | 00 | 0011 | 11 | 1010 |
小阶看齐大阶,也就是将x的阶码由01变成y的阶码11,x的尾数右边移动两位,左边补上00,即x=0.001101*2^11,y=(-0.1010)*2^11
,由于尾数按4位存储,因此舍去x的尾数0.001101的最后两位数01。
小阶看齐大阶之后x,y在计算机中的存储
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 | 0011 | 00 | 0011 |
y | 00 | 0011 | 11 | 1010 |
- 尾数求和
尾数的求和 和定点数的加减法一样,使用补码运算,减法运算转换为加法运算,即A-B=A+(-B)。
在x,y完成对阶之后x=0.0011*2^11,y=(-0.1010)*2^11
。
首先求出二进制浮点数x,y 尾数的补码
二进制浮点数x尾数的原码为00.0011 ,二进制浮点数y尾数的原码为11.1010
根据(负数)反码=原码取反,末位加1,正数 原码=反码=补码的规则计算
二进制浮点数x尾数的补码为00.0011 ,二进制浮点数y尾数的补码为11.0110
将x和y的符号位和数值位一起运算得到尾数求和的结果 S=(x+y)[ 补码]=00.0011+11.0110=11.1001
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 | 0011 | 11 | 1001 |
- 尾数规格化
尾数规格化需要判断两种情况:分别是S[补码]>0和S[补码]<0
S>0 S[补码]=00.1xxx
S<0 S[补码]=11.0xxx
一般情况下,如果不满足上面的格式,需要进行左移,左移N位,浮点数尾数的末位补N个0,依次类推,同时阶码发生相应的变化,以满足规格化的要求。即如果尾数符号位和尾数最高位不一致的情况下需要进行尾数的规格化,如果双符号位不一致的情况下需要右移(定点运算溢出的情况),如果是右移的话则需要进行舍入操作。
尾数规格化之前:
S=(x+y)[补码]=`00.0011+11.0110=11.1001`
将11.1001左移一位,即将尾数的最高位1001的最左边的1舍弃掉,因为尾数数值位只考虑四位,然后在尾数的末位补上0,11.1001 规格化 左移1位的结果是 11.0010
尾数规格化之后:
S=(x+y)[补码]=`00.0011+11.0110=11.1001=11.0010
11.0010在计算机中的存储如下表格所示
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 | 0010 | 11 | 0010 |
尾数左边移动N位,阶码数值位相应的减N位,例如这里x的尾数左移1位,阶码由原来的
0011变成了0010。
S=(x+y)[补码] =11.0010
计算机存储采用的是补码表示法,而我们看到的数据都是使用原码表示法表示的,因此这里还需要将11.0010转换为原码。
而11.0010原码是11.1110,即-0.1110,所以x+y的结果是 -0.1110*2^10
- 舍入
在舍入操作之前需要了解左移和右移
正数的右移,负数的无符号右移,就是相应的补码移位所得,在高位补0即可。
负数的右移,就是补码高位补1,然后按位取反加1即可。
浮点数规格化时如果双符号位不一致的情况下需要右移,而右移动则需要进行舍入的操作,在操作十进制数据时有时候会进行四舍五入,而对于二进制是零舍一入,等效于十进制的四舍五入。
例如S[补码]=10.10110111
,由于双符号位是10,所以是双符号位不一致,此时需要进行右移操作,右移之后S[补码]=11.01011011(1)
,然后进行零舍一入,因为尾数最后面一位是1,因此尾数末位需要加1,即11.01011011+1=11.01011100,
右移的操作,如果考虑阶码的话,阶码也要加1,也就是S[ 补码]=10.10110111舍入操作的结果是11.01011100。
零舍一入可以提高尾数规格化的精度,但是零舍一入可能会发生溢出的情况
例如S[补码]=01.11111111,由于双符号位是01,因此需要进行右移规格化,采用零舍一入,因为最后面一位是1,因此尾数末位要加上1,即00.11111111+1=01.00000000,由于右移动一位之后双符号位是01,即双符号位仍然不一致,还需要进行右移动操作,01.00000000右移一位的结果是00.1000000(0),即S[补码]=01.11111111进行舍入操作后的结果是00.1000000。因为进行了两次右移操作,阶码还需要加10(即十进制的2)。
- 溢出判断
在之前定点数加减法运算时采用了定点数双符号位判断法,即定点数运算结果双符号位一致表示没有溢出,双符号位不一致则表示溢出。
但是在浮点数运算时双符号位判断法并不适用,浮点数运算尾数双符号位不一致并不算溢出,因为当浮点数尾数双符号位不一致时可以右移把不一致的双符号位运算符变成一致。
在浮点数中的溢出判断主要是通过阶码的双符号位判断是否溢出,如果尾数规格化之后,阶码的双符号位不一致,则认为是溢出。
浮点数加减法运算的案例
例如 x=0.11010011*2^1101,y=0.11101110*2^1100
,假设阶码4位,尾数8位,计算x+y的结果
- 对阶
首先将x和y的值在计算机中的存储表示出来,如下表格所示
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 | 1101 | 00 | 11010011 |
y | 00 | 1100 | 00 | 11101110 |
首先第一步进行对阶的操作,按照小阶对齐大阶的原则,因为1101>1100,所以需要将y右移动1位来完成对阶,y=0.011101110*2^1101
,而按照尾数是8位的要求对阶之后的结果如下表格所示
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x | 00 1101 | 00 | 11010011 | |
y | 00 | 1101 | 00 | 01110111 |
- 尾数求和
由于x,y都是正数,因此x[补码]+y[补码]=x[原码]+y[原码]=00.11010011+00.01110111=01.01001010,计算过程如下所示,
x 00.11010011
y 00.01110111
01.01001010
尾数求和之后尾数的符号位结果是01,在定点数运算时算符号位不一致表示溢出,但是浮点数是根据阶码的双符号位判断是否溢出。
- 尾数规格化
浮点数 x+y 尾数求和的结果是01.01001010,由于符号位不一致,因此进行右移操作,01.01001010 右移,即将01.01001010的最后一个0舍弃掉,然后把符号位补上一个0, 即01.01001010 右移1位的结果是 00.10100101
- 舍入
按照零舍一入的原则,00.10100101(0)舍入的结果是00.10100101,最终的结果如下表格所示
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x+y | 00 | 1110 | 00 | 10100101 |
- 溢出判断
溢出判断是判断阶码的符号位,即判断00.10100101
00.10100101的阶码符号位为00,所以没有发生溢出,即x+y的最终结果是
x+y[原码]=x+y[补码]=0.10100101*2^1110
浮点数加减法的运算流程图
3.5.4 浮点数的乘除运算
假设有两个浮点数x,y,可以采用如下表示方法
x=Sx*r^jx
y=Sy*r^jy
对于浮点数的乘法运算是按照阶码相加,尾数求积来运算的,即xy=(SxSy)*r^(jx+jy)
对于浮点数的除法运算时按照阶码相减,尾数求商来运算的,即x/y=(Sx*Sy)*r^(jx-jy)
浮点数的乘除法运算是按照阶码运算->尾数运算->尾数规格化->舍入->溢出判断五个过程
例如二进制浮点数 x=0.11010011*2^1101
,y=0.11101110*2^0001
,假设阶码为4位,尾数8位,计算x*y
x*y=(0.11010011 *0.11101110)*2^(1101+0001)=0.11000100*2^1110
即阶码运算和尾数运算之后x*y的值是0.11000100*2^1110
,在计算机中存储如下表格所示
数值 | 阶码符号位 | 阶码数值位 | 尾数符号位 | 尾数数值位 |
---|---|---|---|---|
x*y | 00 | 1110 | 00 | 11000100 |