这里写目录标题
- 第一章:计算机系统概述
- 计算机的发展
- 计算机的组成
- 计算机的性能指标
- 第二章:数据的表示和运算
- 2.1
- 进位十进制
- BCD码
- 无符号整数的表示和运算
- 带符号整数的表示和运算
- 原反补码的特性对比
- 移码
- 定点小数
- 2.2
- 奇偶校验码
- 算数逻辑运算单元(ALU)
- 介绍
- 补充:电路
- 异或门实现奇偶校验
- 实现加法器
- 并行进位加法器(选做)
- 补码加减运算器(有符号数)
- nbit加法器
- 补码加减原理+设计补码加法器(实现不仅是加法还有减法)
- 判断溢出(有符号数)
- 避免溢出
- 总结
- 加法器中标志位的生成
- 简介
- OF(有符号数)
- SF(有符号数)
- ZF(有无都可)
- CF(无符号数)
- 总结
- 移位运算
- 总览
- 算数移位
- 引入
- 原码的移位
- 反码的移位
- 补码的移位
- 应用
- 总结
- 逻辑移位
- 简介
- 与上面原码位移的区别
- 应用
- 循环移位
- 简介
- 应用
- 总结
- 乘除运算(以定点小数为例)
- 回忆
- 引入
- 原码乘法
- 乘法运算的实现思路
- 硬件实现
- 补充:整数相乘
- 补码乘法
- 与“原码乘法”的对比
- 介绍
- 实现步骤
- 原码除法
- 除法运算的思想
- 硬件实现
- 手算过程
- 总结
- 不恢复余数(加减交替)
- 补充
- 补码除法
- 过程
- 结果
- C语言中的类型转换
- 数据的存储和排列
- 大端法和小端法
- 字节对齐
- 编址单元
- 边界对齐
- 2.3
- 总览
- 浮点数的表示
- 引入
- 浮点数的表示
- 简介
- 尾数规格化
- 左规:提高利用率
- 右规:双符号位溢出后修正
- 不同编码浮点数的“有效值”的界定
- 总结
- 浮点数标准—IEEE754
- 回顾
- IEEE 754标准
- 简介
- 该标准规格化的真值求解
- 真值规格化为IEEE 754标准
- 移码全0/全1的用途
- 浮点数的运算
- 步骤
- 十进制为例
- 二进制
- 注意
- 0、先进行一步进制转换:
- 1、对阶:
- 2、尾数加减
- 3、规格化
- 4 舍入
- 5 判断溢出
- 补充:有舍入的情况
- 补充 判断阶码溢出
- 补充:规避中间过程的舍入
- 强制类型转换
- 常规转换
- int与float
- 总结
第一章:计算机系统概述
计算机的发展
计算机的组成
计算机的性能指标
第二章:数据的表示和运算
2.1
进位十进制
BCD码
无符号整数的表示和运算
带符号整数的表示和运算
原反补码的特性对比
移码
定点小数
2.2
奇偶校验码
算数逻辑运算单元(ALU)
介绍
输入和输出:
ALU的输入有两个数,每个数都是使用一个机器字长的位数来表示数据的,比如上图中A和B都是4位bit,最终计算得到的结果也是4位
进行运算:
ALU中会接入CU的控制,其中,M为1时表示逻辑运算,M为0时表示算术运算
而S有四个bit,所以可以表示2的四次方种功能,所以,可以控制实现算术运算和逻辑运算各16种运算
补充:电路
基本逻辑运算:
基本的逻辑运算,与、或、非,其中,与、非的电路如上图所示,而非门的图就是在输出端加一个空心圆圈
对于基本的逻辑运算,我们有优先级:非>与>或
且有运算律,通过运算律我们可以改造电路,使得电路设计更简单,也就更省钱
复合逻辑电路:
这里的德摩根律非常实用,可以借助此定律利用与或非来实现异或电路,(ps:注意上图中的图,A、B线有交叉,注意不要看岔)
上图圈起来的是各个逻辑对应的电路图标
同或:对异或取反
异或门实现奇偶校验
首先对于奇偶校验来说,当所有位的1的个数为奇数个的话,结果为1,当有偶数个,结果为0
这正好符合异或的特性,从定义来说,相同的异或出来的结果是0,不同的是1,所以计算下去,结果与奇偶校验的结果一样
同时,我们也可以利用异或的一个技巧,即消消乐,最后剩下什么,结果就是什么,如上图最后剩下0,结果就是0
实现加法器
一位加法:
A、B是两个加数,i表示当前计算的是从右向左第几位
Ci-1,表示来自i-1的进位,也表示当前 i 所需要加上的进位;而Ci,表示的则是当前第 i 位向更高一位的进位是0还是1
上图中需要ps的是:关于Ci的计算,有两种情况,这两种情况是或的关系,其中一个是AB都为1,另一个是AB至少一个1,且C也是1
图像化类比:
串行加法器:
多了一个进位触发器,用于保存上一次计算而来的Ci,用于参与下一位的计算中
串行进位的并行加法器:
并行加法器,就是同时有多个串行加法器,将输入与输出拼接起来,就可以同时输入多个A、B的位进行运算了
但是!由于左边的加法器仍然依赖于右边对于Ci-1的输入,所以,他还是依赖于串行计算,因此叫做串行进位的并行加法器
从右下角的逻辑表达式也可以看出:每一步的计算都依赖于上一步的C的产出
并行进位加法器(选做)
优化思路:
虽然说每一位的C都需要上一步的计算结果,但是上一步的计算是上上步的计算,以此类推,就会推到最开始的C0
我们只需要使用最开始的C0,再加上每一步的A和B,就可以计算出任何一步的C了
优化效果:
我们将A&&B设为G,A^^B设为P
可以看到,计算得到的G2、P2会被后面计算Cn(n > 2)时用到,
计算得到的G3、P3会被后面计算Cn(n > 3)时用到,…等等
所以哪怕优化思路是使用最初的C0计算,但是有了上面的思路,每一步的计算都对后续的计算有贡献,也会提高很大的效率
而最初的计算所需的值是已知的值,计算会瞬间完成,所有可以认为后续计算所需的材料会瞬间准备好,所以,后续某一步在求的时候,直接拿着所有已经准备好的数据去计算即可,所以可以认为是并行计算,可以同时进行多个位的加法
缺点:
电路会越来越复杂
应用:
为了避免太严重的套娃,我们一般只套到第四层,所以也就是只计算四位C1,C2,C3,C4,所以一般用于4位ALU的加法器
补码加减运算器(有符号数)
nbit加法器
基本n位加法器原理:
经过上面的单个bit位加法器的研究,现在我们来看一下最终的一次性nbit位加法器原理:
如上图所示
拓展:
而如果我们将Cout视为下一个加法器的Cin,那么,两个n位的加法器,最终就会形成一个2n位的加法器
补码加减原理+设计补码加法器(实现不仅是加法还有减法)
首先,先看补码的加减原理,加法时直接加,减法时,需要将其转换为加法:将减数按位取反+1,变为其相反数的补码,之后相加
接下来,我们依照这个原理来设计电路,实现补码加法器:
首先对于第一个数,无论加减都直接输入到加法器中
第二个数,注意,右边有一个sub,在加法时他会在最低位补充一个0,如果是减法,他会在最低位补充一个1
当为加法时,Y也直接输入到加法器,再加上sub补充的0,实际上就是X+Y
而当为减法时,Y会进入左边那条路,首先经过非门,每一位按位取反,之后加上sub,此时sub会补充一个1,所以就实现了Y全部按位取反再+1,也就实现了减法
例:
可以看到,当我们使用真值-8和7的补码形式进行加减时,加法没有问题,减法的真值是-15,超出了4bit补码所能解释的真值的范围,所以发生了溢出,这个结果是错的,由于超出了表示范围所以错了
同样,相同的电路还可以用于无符号整数的加减法,因为无符号整数的加减与带符号整数的加减的二进制操作是一模一样的,只是最后的解释体系不同(无符号操作的是原码,带符号操作的是补码,且最后要根据正负,转为原码)
无符号整数的例子:
无符号所能解释的真值的范围是0~2的n次方-1
所以15显然超出了其表示的范围,由当前的加法器是计算不出来的,溢出了
判断溢出(有符号数)
补码的加减法,可以去平板笔记上复习,总结来说就是使用补码进行加减法,减法时转换为加法,最后的结果要转为原码求真值
上图中,第一个的计算结果超出了8位补码所能解释的真值的范围:-128~127,所以会发生溢出,计算出错误结果,如-117是错误结果
判断溢出:
溢出有下面一些规律:
1、只有同符号相加才会产生溢出,因为异号相加只会让结果的绝对值更小,不会溢出
2、溢出后得到的错误结果,其符号一定会与原来的加数相反
3、真值1+真值2,如果溢出,是以循环的方式,向后数“真值2”个数,类似于取模的操作,但是由于这里是有符号整数,所以没有很直观,
如果是无符号整数,那么3 bit的表示范围是0~7,如果是5+4,那么二进制加完之后,将前面溢出的位舍弃,最后的结果是1,与上面一样,是以循环的方式向后数第4个数,得到1,但这也是(5+4)%8的结果,之所以%8,是因为表示范围是0到7,一共8个数
判断溢出的方法一:
将其用逻辑符号化,就是上图所示的式子,其中,上式的左边部分是表示负数+负数的溢出,而右边的部分是表示正数+正数的溢出,二者最终取或,就可以判断是否有溢出了
判断溢出的方法二:
通过判断符号位的进位以及最高数值位的进位,来看是否发生溢出
最高数值位的进位:就是除符号位之外的数值位中,最高数值位的计算结果是否发生进位,是否向符号位进位,进位则为1,记作C1
而符号位的进位:顾名思义,就是符号位的的计算结果有没有产生更高的进位,有则为1,记作C2
当C1与C2不同时,表示发生了溢出
而判断是否相同,使用一个异或即可:
判断溢出的方法三:
使用双符号位,即运算之前复制一个符号位,两个符号位参与计算,最终,如果计算结果的双符号不相同,则为溢出,
其中,如果是01,我们可以视为,本来应该是0,结果计算结果是1,而原计算数是0,所以,是从正到负,发生了上溢
如果是10,我们可以视为,本来应该是1,结果是0,而原计算数是1,所以,是从负到正,发生了下溢
总之,如果最终两个符号位不同,则发生了溢出
而,对于双符号位补码,又称为模4补码,因为模4的话,范围就是0~3,那么二进制就是2位二进制就可以表示0到3,双符号位就是2位符号位,所以是模4补码
对于单符号位补码,则称为模2补码
注意:
这种方式实际上存储的还是一个符号位,运算时会复制一个符号位,进行运算
避免溢出
可以进行符号拓展,这样,n就被拓展了,而为什么此时可以不再依赖机器字长,而可以依赖于变量的字长,是因为变量会对内存单元进行封装,或者拼接,在变量的视角内,他将几块物理内存单元视为一整块单元,就可以进行拓展了,具体解释可以参考平板上2.1.4.1的笔记
需要注意的是:
整数拓展时,是在符号位和数值位之间;且原码补0,反码补码补1
小数拓展时,是在最后;且原码补码补0,反码补1
总结
加法器中标志位的生成
简介
在一个加法器中,不仅会输出n bit的相加结果,而且会输出四个标志位,分别为OF、SF、ZF、CF
OF(有符号数)
首先要注意一点:OF标志位,仅对当前加法器是计算的"有符号加减"时才有效,对于无符号借用该"补码加法器"时,该OF是无效的
例子:
对于补码,都是有符号数运算才会使用补码,
X+Y,可以看到,最高位进位 异或 次高位进位,最后的结果是0,表示不溢出,结果正确。
而X-Y,可以看到,最高位进位 异或 次高位进位,最后的结果是1,表示溢出,结果错误,该计算结果无效
总结:
使用的是上面“判断溢出(有符号)”中的方法二的原理
SF(有符号数)
SF,表示有符号数加减运算结果的“正负性”,SF=0表示正数,SF=1,表示负数
原理:
计算结果最高位是多少,这个标志位就是多少
例子:
如上图X、Y
X+Y,最高位是1,表示结果为负数
X-Y,最高位(将溢出位舍去)是0,表示结果为正数
PS1:SF标志位只是表示所计算出来的二进制数的符号性,并不关心结果是不是对的,比如上面的X-Y,虽然发生溢出,结果是错的,他还是会按照错的这个二进制来表示SF
PS2:SF也是只对有符号数有效
ZF(有无都可)
如果运算结果为0,则该标志位为1,否则为0
PS:ZF标志位对有符号数和无符号数都适用
CF(无符号数)
表示,加法中的进位,减法中的借位是否发生,发送进借位则CF=1
例子:
无符号数加减:
首先我们要知道,这个例子的二进制数与上面有符号数的补码表示法的二进制数是一样的,主要在于解释系统不同,一个为无符号解释系统,一个为有符号解释系统,所以导致了运算结果的解释也不一样,是否溢出也不一样,且无符号数用的是原码,因为只有原码,而有符号数用的是补码
CF只能用于无符号数
对于X+Y,可以看到,他的最高位“产生”的进位是0,而且sub为0(因为是加法)
二者异或,结果为0,表示没有进借位,而由于是加法,所以可表述为没有进位,那么也就是说最高位没有进位,也就是没有溢出,结果正确
对于X-Y,可以看到,他的最高位“产生”的进位是0,sub为1(减法)
二者异或,结果为1,表示有进借位,而由于是减法,所以是有借位,表示被减数比减数要小,这对于无符号数来说,产生了溢出,结果错误
总结:
所以,可以借助CF来判断无符号数是否溢出
补充:
所以,对于两个数的加减法最后有没有溢出:
对于有符号数,使用OF标志位来判断
对于无符号数,使用CF标志位来判断
这也解释了为什么一个例子中同一对二进制数相加减,最后的结果、溢出情况等都不相同,因为他们采用了不同的解释系统,从给出的X
、Y的解释就开始不一样了,
有符号数:
一个是采用的是真值的补码,后续的整个解释系统、包括对最后二进制数结果的解释、是否溢出的解释、判断方法、标志位等,都要使用补码解释系统、有符号数系统
无符号数:
一个采用的是真值的原码无符号表示(因为无符号数没有补码,只有原码),最后对二进制结果的解释、对是溢出的判断、标志位等,都要使用无符号数系统
总结
移位运算
总览
算数移位
引入
可以看到,十进制数通过移动小数点可以实现乘除运算
原码的移位
右移:
原码的位移,是二进制的位移,其右移表示真值除以2,但是如果右移丢去的是一个1,那么该1本来表示的是2的-1次方,也就是0.5,现在由于右移把他丢弃了,所以也就造成了精度损失,本来-5/2的结果是-2.5,结果由于精度损失,经过位运算得到的5/2的结果是-2
也就是说,对于右移来说,其除法相当于是整除,也就是直接去掉小数部分
左移:
左移,也就是真值乘2,但是对于一个8位符号位的数,那么只有7位数值位,那么其表示的范围就是-(2的7次方-1) ~ 2的7次方-1,(因为2的7次方-1就是7位二进制全为1)
那么当我们一直右移的时候,就可能会导致其正确的结果,超出了当前所能表示的最大范围,其最高位的1,也就是权重最高的1会丢失,那么就会造成严重的误差
定点小数同整数的规则一样:
反码的移位
对于正数而言,其反码与补码相同,所以,移位的运算与原码相同
而对于负数而言,其反码与原码取反,他的规则是,位移之后的空缺位,用1进行填补
PS:对于反码和补码而言,想他们所表示的真值,要将其转化为原码,通过原码转为十进制,则为他们所表示的真值,也就是说,反码和补码直接将他们的二进制字面值转为十进制没有任何意义,或者说,禁止直接将反码和补码的字面值直接转为十进制
所以,对于位运算也一样,我们对反码和补码做的操作,都是对应其原码的操作,也就是对应其真值的操作,最终的操作效果是:真值乘或者除
补码的移位
由于补码,从右向左数第一个"1",在这个“1”的左边(不包含该“1”),其与反码相同,在这个“1”的右边(包含该1),其与原码相同
所以,在进行位运算时,如果是左边缺位,那么就补1,如果是右边缺位,那么就补0
应用
当我们想要计算-20 乘 7的时候,可以将7拆分为二进制各个位权,之后使用分配率,就转换为了-20的几个位运算
将这几个位运算的结果相加,就实现了-20 乘 7
由此可见,乘法是由“位运算”配合“加法”实现的
总结
逻辑移位
简介
运算规则仍然是缺位补0
与上面原码位移的区别
上面的原码是带符号数,有符号位和数值位,他在位运算时,是将所有的数值位进行位移
而这里的是无符号位,将所有的位进行位移
应用
即实现不同字节位置的调整
循环移位
简介
循环移位,就是将移出来的那个位,补充到空缺的那个位的位置上,称之为循环移位
补充:
进借位:进借位的作用,当我们的寄存器是n bit时。我们进行n bit位的加减时,最终的结果是n bit位,如果溢出,我们直接舍去
但是当我们要进行多字节的加减,不止n bit时,这时我们只能先算低字节,再算高字节,那低字节最高位如果产生了进位,我们不可能将其直接舍去,因为他肯定要参与更高字节的运算,所以,此时就由CF来记录进位
进位位循环位移:
就是将CF也算进来,一起进行循环位移
应用
对于大端字节序和小端字节序的转换,假如说我们现在有一个2字节的大端字节序,想要将其转为小端
那么只需要将该二进制的高8位,依次进行循环位移,其最后就会变为小端字节序,且被移动的那个字节,其内部的顺序是不变,不会产生镜像的,因为最高字节的最高位会先到最低位,之后,原本最高字节的次高位,会移动到新的最低位,依次类推
我们移动的那个字节的相对位置并没有改变,所以,是完全正确的,其字节内部的顺序也没有乱
总结
乘除运算(以定点小数为例)
回忆
定点小数与定点整数的区别:
定点小数和定点整数,
1、首先要明确,定点小数指的是只有小数部分,定点整数(带符号整数)指的是只有整数部分;二者混合,即既有整数也有小数,那是浮点数,不是定点数
2、上图中,最开始的那个“1”都是符号位,定点整数使用“,”(逗号)隔开;定点小数使用“.”(点)隔开
引入
引入:
首先,我们来看十进制乘法,可以看到,每一行都要比上一行错一位,这是因为,计算乘法时,是将被乘数与乘数的每一位小数进行相乘。最后相加,而每一位小数,他的权重是依次递增的,所以,每一位小数与被乘数相乘的结果,是会错开一位的
对于二进制:
其运算的规则与十进制一样,每一位与被乘数相乘,然后错位写下来,1与被乘数相乘就是被乘数,0与被乘数相乘是0
最后相加,得到结果
其二进制原理:
同样,将乘数按照位权依次拆开,将被乘数按照最低位权拆开,
之后,将其使用分配律,得到上图式子,可以看到,该式子就是“被乘数”乘某些位权,最后相加,
其实就是被乘数的几个位运算的结果,进行相加
考虑如下问题:
1、上面我们的例子都是两个正数进行乘积,但是实际计算中,数字是有正有负的,那遇到符号位我们该如何处理
2、例如,上面我们的被乘数占用5个bit,乘数占用5个,最后的结果占用了9个,将近增加了一倍,但是假如我们CPU的寄存器只有5个bit呢,该如何处理
3、我们在计算乘法的过程中是否需要把每一步的位运算结果都保存下来,如果是这样,那万一某个被乘数有10086个小数位,那要保存10086个位运算后的结果吗
原码乘法
乘法运算的实现思路
符号位的处理:
对于符号,我们将其单独处理,因为十进制下我们也是这样干的,而且,正数与负数相乘,结果肯定是负数,正正相乘或者负负相乘,其结果肯定为正数,且异或的正数表示为0,负数表示为1,异号则为1,表示负,同号为0,表示正,这正好与异或的特性相同
数值位的处理:
然后,取绝对值进行数值位的运算,其原理就用上面的“位运算”+“加法”
硬件实现
实现步骤:
首先,要明确,X寄存器存放被乘数的二进制数(含符号位);MQ寄存器存放乘数;ACC存放部分积(也就是每一步的临时结果),其初始化为0,其实部分积不仅会存放在ACC,还会存在MQ,继续往下看
第一步,检查MQ,也就是乘数的最低位是几
第二步,如果是1,那么将ACC 加上 X中的被乘数
第三步,将计算结果更新回ACC
第四步,将ACC与MQ的内容整体右移,且ACC的地位移动进入到MQ内
第五步,由于第四步的右移,MQ最低位被移出(直接舍弃即可,因为他的作用在第二步已经发挥了)
将上面的五步,进行n次即可(有n位的数值位),或者说,进行完第四步后,当MQ中的乘数只剩下符号位的时候,就可以停止了
最后,计算出符号位,替换到ACC最高位即可
手算模拟:
与上面机器的过程一样,手动模拟一遍即可,最后要进行n次相加和右移,即可得到结果的绝对值(ACC拼接上“MQ的除去最低位的所有位”或者说“MQ的前n位”)
最后,再加上符号
计算完成
补充:整数相乘
整数与小数的过程一样,只不过最后小数点的位置应该如上图所示
补码乘法
与“原码乘法”的对比
区别一:
区别二:
原码的右移是空缺位直接补0,而补码的右移则按照补码右移的规则:正数的话与“原码右移”一样;负数的话,右移则空缺位补1
区别三:
1、原码乘法的符号位不参与运算:即乘数运算到符号位就停止右移了
但是补码的符号位要参与运算
2、原码乘法的最终符号的确定要使用异或来单独处理
而补码乘法的符号是在运算过程中确定的
介绍
1、辅助位:
辅助位就是MQ的最右边再加一位,他会保存从MQ最低位移出来的位,辅助位初始化为0
2、符号位:
由于MQ多出了一位,而一般CPU的寄存器的位数都是统一的,所以,X也增加一位
不过,X是在最左侧加一位,表示符号位,这样,X就有两个符号位,所以是存放双符号位补码
而MQ仍然是存放单符号位补码
上面的ACC也是双符号位
对比原码:原码只存放绝对值,也就是X和MQ符号位永远都是正数(即0)
3、辅助电路:
由于该运算会涉及到加“-x的补码”,所以,会有一个辅助电路可以实现将X的内容转为“-x的补码”
实现步骤
1、起始状态下,辅助位初始化是0,起始时就要根据赋值位-最低位,来进行第一次加法
2、在计算过程中,MQ的符号位后面的“点”一直在,这个是正常的,之前原码乘法也应该有“点”(但是当时的图上并没有),这个不会影响,有和没有都可以
3、在进行完加法之后,在右移之前,要根据加法结束后ACC的正负性,来判断右移后补0还是补1
4、最后进行完n轮加法和右移之后,要在进行一次加法
5、最终,加完后的ACC 拼接上 MQ中的前n位(MQ一共有n+2位,其中多出来的2个一个是“最低位”(注意此处的“最低位”不是物理意义上的“最低位”,物理上的“最低位”就是辅助位,这里讲的“最低位”是辅助位高一位),一个是辅助位)
6、最终结果的正负已经体现在了ACC的符号位上,不用再单独求了
原码除法
除法运算的思想
十进制:
十进制除法计算时,会得到一个商以及余数,下面我们抛开余数,来看商和除数的乘积(如上图左下角)
先把商按照位权拆开(不同位权与系数相乘之后相加),然后除数按照绝对值最大的位权拆开
之后,使用分配律相乘,可以得到几个数相加,这几个数就是右边那个竖式计算过程中,每次“上”一位商之后的计算结果,可以理解为这些值都是构成“被除数”的有效值
二进制:
步骤:
1、忽略小数点(因为被除数和除数的小数位数相同,按照一般除法的思想,约去相同位数,最后的效果与“直接忽略掉小数点”相同),之后开始确定商的当前位,并得到余数(少一位)
2、对余数末尾补0
3、再次确定商的当前位
4、等商确定到了5位即可停止(包括前面的0)
可以发现:
1、每一步得到的局部有效结果要么是0,要么是除数:01101;对应到不忽略小数点的竖式就是每次要么是0乘位权,要么是0.1101乘位权(这里乘的位权都是2的负几次方,这样的位权,注意不是10的负几次方,因为这是二进制,注意不要被惯性带跑)
2、最后计算余数时,其小数点的位置与被除数最初的位置一样的,向下画一条直线顺下去即可
硬件实现
前提了解:
首先,我们需要知道,这种原码除法叫做恢复余数法
1、对于结果的符号位,我们仍然采用异或的方法来处理;对于数值位,则采用其绝对值来进行除法运算
2、注意ACC存放余数(刚开始初始状态存放被除数)
3、MQ中的最低位存放当前要确定的商的当前位
实现步骤:
因为手算时,商的当前位上0还是1,是根据当前的余数与除数的大小确定的,当余数比较大时,说明可以上1,当余数比较小时,说明只能上0
1、计算机首先不管三七二十一,先给当前位上一个“1”
2、之后,根据减法确定ACC中存放的余数(初始状态下存放的是被除数,但是我们直接将其视为余数就好了),与除数,谁大谁小,将ACC中的数 减去 除数,也就是ACC的补码 加上 除数的相反数的补码;而由前面可知被除数和除数都是绝对值,所以,ACC中的是正数,其补码等于原码,而虽然除数是正数,但是这里要加上除数相反数的补码,所以这个肯定为负数,所以补码需要求出
3、将ACC 加上 除数的相反数的补码的计算结果,更新回ACC
4、判断更新回ACC的数的符号位是0还是1,也就是判断是正数还是负数。
(因为补码转为原码之后,符号位是不变的,所以这里不用进行转换,直接看补码的符号,就知道对应的结果的正负)
5、如果是负数,则说明搞错了,将商的当前位改回0
6、恢复余数:上面第五步判断出搞错了,所以要进行余数的恢复,我们将当前的ACC 加上 除数的补码 就可以恢复成原来的ACC了
(因为此时ACC仍然是补码,所以可以直接进行补码运算)
7、而运算回去的结果必然是一个正数(因为恢复成原来的余数,而原来的余数不可能为负,因为余数为负都会被恢复),所以,最后的结果的补码和原码也是相同的,所以,就无需任何的转换,直接更新回ACC
8、为了实现左边竖式的错位相减,同时末尾补0的操作,我们将ACC和MQ整体左移,就可以实现了
9、最后,将空出来的MQ的最低位,补0
如果第四步判断出来被更新后的余数是正数,那么直接跳过4567,来到第八步即可
每一位都这样确定,到最后一位时,进行到第八步之前(确定商的最后一位时,ACC初版为正数:直接结束;ACC初版为负数:进行4、5、6、7,然后结束),就可以结束了
最后结果:
1、确定小数点的隐含位置,得到商的绝对值,以及余数的临时值
2、将余数的临时值,乘以2的-n次方,才是最终真实的余数,(题目中机器字长为5,除去一位的符号位之后,n=4)
3、最后将商的正负号更新为异或的结果
补充:
最后最终的余数的正负性与商相同,然而计算过程中,我们都是取的绝对值,所以这里的最终不是指的最后一步的余数,而是将余数乘以2的负n次方之后的那个结果,要将其符号与商的符号统一
(纠正:不一定符号相同)
手算过程
第一部分:是需要恢复余数的
第二部分:是不需要恢复余数的
总结
不恢复余数(加减交替)
PS:最好对比着上面“恢复余数”的“手算过程”来理解
这里简化之后,使得余数为负、余数为正,二者之间的步骤是一样多的
首先还是一个余数减去除数
根据结果,
若余数为负,则确定商的当前位是“0”;之后左移等待进行下一位的余数计算,从而进行商的下一位的确定
若余数为正,则确定商的当前位是“1”;之后左移等待进行下一位的余数计算,从而进行商的下一位的确定
进行下一位余数的计算时,
若上一位商的是“0”,则将“左移后的结果” 加上 “除数的绝对值”
若上一位商的是“1”,则将“左移后的结果” 加上 “除数相反数的绝对值”
该方法与“回复余数法”一样,都是在左移之前就确定了商的当前位
补充:
1、符号位仍然使用异或确定;余数的正负性与商相同;最后最终的余数的正负性与商相同,然而计算过程中,我们都是取的绝对值,所以这里的最终不是指的最后一步的余数,而是将余数乘以2的负n次方之后的那个结果,要将其符号与商的符号统一
2、如果计算过程中的最后一步的余数为负数,也需要将其恢复,恢复为正数
补充
由于我们上面的都是“定点小数的除法”,所以规定要使用上面的算法与以及硬件,必须满足x < y,也就是必须最后的商是0.xxx…;否则就会出现>1的数,而硬件在检查这一条件时,检查第一个商的是不是0,如果是0则可以使用,否则不可以
( 关于“ 定点整数与定点小数的区别,可以看上面的“回忆” ”)
补码除法
过程
该过程与上面“原码除法”的“加减交替法”很像
1、与原码不同的是:符号位参与运算,且采用双符号位
2、与原码不同的是:原码初始的第一个余数,一定是减去除数(即加上除数的补码),而这里要根据被除数补码 和 除数补码 的符号,来决定初始第一个余数如何计算
(同号则减,异号则加)
3、过程中,虽然仍然是“左移之前确定商的当前位,以及根据商来确定加除数还是减除数”,
不同的是确定商的当前位:
余数与“除数的补码”,同号,则商1
余数与“除数的补码”,异号,则商0
而
商1 ,则减去除数(加上除数相反数的补码)
商0 ,则加上除数(加上除数补码)
结果
1、商的最后一位恒定确定为1
2、最后的商的结果是“真值”的补码
(这个结果却是可能与除数相同,毕竟里面涉及到约分等操作)
3、余数需要通过移位得到最终的值,且余数的符号位是正确的,无需改变
(余数的正负性是否一定与商相同,还有待考究;在“原码除法中的“补充”以及““总结”中的补充””也是有待考究)
(答案是不一定,此处不一定,在“原码除法中的“补充”中是一定的;数学定义上不一定)
C语言中的类型转换
首先注意:C语言中的数据内容可以想象为都是二进制数据,注意这些二进制都是补码形式
1、同一个类型,带符号或者不带符号,改变的只是解释方式,其底层的二进制数据没有改变,所以转换之后,同一串二进制,会得到不同的真值
2、当长整形转为短整形,会造成字节缺失,C语言中会截掉高位的字节
3、短整形变长整形,会进行补位,由于是补码表示,所以补的二进制都是按照补码补位的规则:
负数且在左边,补1,正数补0;(如果是负数在右边补的话,是补0,但是C语言中的拓展都是往左边)
PS:上图中的0x…,都是对整体四字节数据内容的十六进制表达罢了
与大端法还是小端法存储没有关系,因为大小端需要知道在内存地址中是如何存数据的,以及最后内存块解释出来的数据,才能推算出是大端还是小端
补充:大小端
分别向两种端存储法写入数据:0x12345678
我们将两块地址内存都摆为从左到右依次是从低到高的地址
那么,其数据存放的情况是:
大端:按顺序存放,先写入“数据高位”,所谓高位在前
小端:每个字节内部不变,但是字节之间的顺序颠倒,先写入“数据低位”,所谓低位在前
但是对于上图中的情况,我们要是对这四个字节进行解释的话,大端法小端法最后都会解释为0x12345678
所以,对于同一个数据,他们的最终解释的结果是一样的,不同的是对于一个内存块,大端法是从左向右解释,得到数据;小端法是从右向左解释,得到数据(或者说逆着地址递增的方向解释数据,因为他就是逆着放的)
例:
A是大端法,B是小端法
A向B发送数据:0x12345678
B收到数据,将数据写入自己的内容,由于是网络传输,先收到的字节先写入,所以就会按照“大端法”的写入顺序写入,但是由于B系统是小端法的解释系统,所以就会被解释为数据:0x78563412
所以这就是为什么,在网络字节传输之前,从本地到套接字需要进行字节序的转换,不然解析到的数据是错误的
数据的存储和排列
大端法和小端法
上面的补充部分已经介绍了大小端
小端模式的优点:
由于小端法是前面的地址先存放“数据低位”,所以,这符合计算的逻辑,比如,CPU要计算加法,那么任何的计算都是从“数据低位”先开始进行运算,所以,小端法更便于机器处理
字节对齐
编址单元
以字节编址:
以半字编址(一字长为4字节为例):
以字编址(一字长是4字节为例):
而,不管是“半字”还是“字”编址方式,最终都要转为“字节编址”方式
转换方法:
半字下2号内存->字节下4号内存(将半字编址下的内存编号,左移一位,也就是乘2,就可以得到字节下编址对应的内存编号)
字下2号内存->字节下8号内存(将字编址下的内存编号,左移两位,也就是乘4,就可以得到字节下编址对应的内存编号)
边界对齐
边界对齐时,由于访存一次只读写一个字长,所以,存放数据时,如果下一个要存放的数据类型所需的空间太大,当前字长内剩余的空间不够放,则直接将这些内存填充,往下一行存放数据,做到边界对齐
(之前C++中说的是按照结构体的最大字节成员的倍数进行对其,但此处是按照系统的字长来对齐,二者说法都有道理,因为对其策略会受到不同的系统、不同的编译器所影响,所以,具体领域具体分析)
补充:
2.3
总览
浮点数的表示
引入
我们来看一下科学计数法,对于科学计数法,我们还可以换一种方式来表示,如上图,左面表示次方的正负以及大小,右边表示数值的正负以及大小,而由于左边表示的次方反映了数据的数量级,所以阶码反映数值的大小;右边表示的数据反映了在当前这个量级下的系数,这个份系数的小数部分位数越多,精度越高,所以尾数反映了精度
定点数与浮点数:
浮点数的表示
简介
阶码使用定点整数表示,尾数使用定点小数表示
注意,解码的底可以使用2的n次方作为底,因为可以转为2的某次方
注意:阶码和尾数一个是定点整数,一个是定点小数,但是阶码是其补码形式或者移码形式;尾数是其原码形式或者补码形式
例1:
首先看a:
题目中说的是阶码和尾数都是补码形式(正数的补码与原码相同,负数的补码与原码有技巧转换)
先看阶码:定点整数中,逗号前面是符号位,0表示正数,所以阶码的补码就是其原码,使用原码翻译为真值是:+1
再看尾数:定点小数中,“点”前面是符号位,1表示负数,所以后面的补码要先转为原码,再转为真值,最终尾数真值为:-0.0111(二进制形式)
之后相乘,也就是进行小数点移动
可以看到,1B正好可以存下这个浮点数的表示
例2:
先看阶码:定点整数中,逗号前面是符号位,0表示正数,所以阶码的补码就是其原码,使用原码翻译为真值是:+2
再看尾数:定点小数中,“点”前面是符号位,0表示负数,所以后面的补码就是原码,转为真值,最终尾数真值为:+0.01001(二进制形式)
之后相乘,也就是进行小数点移动
可以看到,1B无法可以存下这个浮点数的表示,最右边的一位会被舍弃,从而造成小数位数的减少,造成精度的缺失,解决方案就是接下来的“尾数规格化”
尾数规格化
左规:提高利用率
左规:
同样类比科学计数法,科学计数法中规定,小数点前一位必须是一个有效值,如果不是有效值,则通过移动小数点+改变量级,使得其优化为符合规定,且小数点前只能有一位有效值,此规定可以保证精度是拉满的
类比十进制,对于二进制的尾数,我们同样可以规定其符号位的下一位必须是有效值,也就是“点”的后面必须是有效值,为了达到这个规定,我们可以移动小数点的位置,同时配合量级,使其保证“点”的后面第一位是有效值
其中,我们把左移规格化称为左规,也就是向左移动让其提高精度
右移规格化称为右规,也就是向右移动让其提高精度
(下面是一个右规的例子)
右规:双符号位溢出后修正
右规的使用场景一般用于某个计算结果发生了溢出,并且该结果使用了双符号位
这个时候,可以使用右规,一方面,可以让其精度提高,另一方面,也是最重要的方面,此时由于计算结果的溢出,双符号位中的左边是符号位,右边其实是数值位,(如上图,a为符号位,b其实存储的是数值),所以,使用右规,将数值放到“点”的后面,同时将阶数加一,保证了数据的正确
所以,我们说采用“双符号位”可以挽救溢出发生后的情况
注意:不管是左规还是右规,在修正完尾数后,记得还要修正与之对应的阶数
不同编码浮点数的“有效值”的界定
要注意,由于尾数可以用原码也可以用补码表示,所以,当负数用补码表示时,与其他情况会不一样,负数补码下,0才是有效值
再次注意,在调整完尾数后,阶数也要跟着调整
总结
浮点数标准—IEEE754
回顾
回顾一下移码,之前我们讲移码,都是将补码的符号位进行取反就是移码,这些只是记忆的技巧,实际上他的定义是:真值(二进制形式)+偏置值(2的n-1次方,上题中n为8)
当然,偏置值还可以取别的值:
其中:-128的移码计算过程:
移码属于有符号数,(取证见享做笔记,关于“移码”的部分),而真值和偏置值都可以视为无符号整数
注意,此处是真值的二进制形式+偏置值的二进制形式,该计算过程是最原始的进借位计算,因为符号都被提出来了,不在二进制数内表示符号,所以真值和偏置值都可以视为无符号整数,所以,移码的计算过程可以使用“无符号数的加减”进行计算,得到移码的字面值,只不过最终移码属于有符号数(因为其对应的真值有符号)
即,计算移码字面量的过程,使用无符号数的计算逻辑,但是最终的解释权给到有符号数
由于移码的计算过程采用无符号数的计算,这些二进制数一旦溢出都会进行取模,最后的结果也会对2的n次方取模,所以,我们在计算的时候如果不够减,直接加上一个2的n次方即可。
(只有真值:-128会用到借一个2的n次方,其他的都比偏置值小,不会用到这个)
关于取模的取证(2.2.3 #### 判断溢出(有符号数))
注意**-128对应的移码是全1,-127对应的移码是全0**,而之后,其对应的移码如果用无符号数来解释字面量的话,其无符号数值会递增,从1到254,实际上对应的真值也是递增,从-126到+127
IEEE 754标准
简介
格式:
该标准与我们那些常规标准有很大的不同:
1、尾数的正负性,也就是数符,被移到了最前面,表示尾数的正负
2、尾数最高位不再是符号位,因为数符被移到了最前面,所以现在尾数的最高位是数值位,在此基础上,754标准规定该数值位必须是1,也就是数值位的最高位强制是1(所以想与真实值匹配只能去改变阶码),同时这个1会省略,即不表示出来
3、754标准的偏置值不再是2的n次方,而是2的n次方-1
4、阶码部分用移码表示,尾数部分用原码表示;其中,阶符(也就是阶码的正负性),包含在移码中
特殊情况:
在754标准中,阶码全0和全1被占用,所以8位阶码对应的真值的范围是从-126~127
该标准规格化的真值求解
利用该标准进行规格化:
首先是根据数符确定真值的正负
之后
1、数值位的“1”补上
2、求阶码的真值:
移码-偏移量
而求阶码真值的时候,将移码的字面值使用无符号整数解释为十进制 减去 偏移量的无符号整数的十进制解释 就可以得到真值的无符号整数十进制解释,而真值和偏移量本来就是无符号整数,所以就可以直接得到真值(十进制形式)
(当然也可以使用二进制的无符号整数加减,最后将二进制无符号整数转为十进制)
真值规格化为IEEE 754标准
将一个浮点数真值->IEEE 754标准的二进制数
1、数符为真值的正负,正数为0,负数为1
2、尾数先转为1.xxx的二进制形式,之后在IEEE规格化后,省去第一个1
(如上图箭头所示)
3、求移码时,由于真值和偏移量都是无符号整数,所以,求移码的字面量时,可以直接采用无符号整数的加减(第一种是无符号二进制加减,可以求得阶码真值的字面量,直接放到IEEE中即可;另一种是将真值和偏移量的无符号整数解释出来的十进制相加减,最后将十进制的结果转为无符号二进制,放入IEEE)
注意一定要凑足32位(且是1+8+23)
将一个IEEE 754标准的二进制数->浮点数真值
1、将题目给出的十六进制,先转为二进制
2、要注意别忘了,阶码的真值=移码-偏移量
(使用无符号整数计算,A-B,如果A>B,那么结果就是真值;如果A<B,那么结果就是B-A的无符号数,前面再加一个负号),因为我们说的真值是无符号数是将其符号除去,所以计算出来的无符号数只是真值的绝对值,如果小减去大,要加负号,真值的表示范围是-128到+127,但是由于-128 -127被用作特殊用途,所以实际上真值的范围是-126到+127
特别注意:虽然我们说真值是无符号数,但是我们说的是真值除去符号之后的绝对值是无符号数,且真值的无符号数与之前那张表第三列的无符号数无关,那个是移码的无符号数解释,与真正的无符号数范围八竿子打不着,可以说是毫无关系,真值绝对值的无符号数就是最正常的不带符号位的二进制,只表示正数,所以只表示绝对值,我们可以在该无符号数前面手动增加“负号”
3、最后2进制数乘一个阶码,可以把小数点进行位移,也可以先将乘数、阶码都转为十进制相乘
移码全0/全1的用途
如果超出了该标准的最大最小值范围怎么办?
0、当移码的无符号解释在1到254时,即对应真值范围在-126到+127,这个是正常的表示范围
1、当阶码全为0时,有以下用途
尾数不全为0:可以表示比正常范围内最小值更小的值;规定隐含最高位变为0,且阶码真值永远翻译为-126(尽管解码部分实际上是全0),此时能改变浮点数大小的操作,只有改变尾数了
尾数全为0:表示0(可以是+0,可以是-0)
2、当阶码全为1时,有以下用途:
尾数全为0:表示无穷大
尾数不全为0:表示非法运算(通常发生在0/0,无穷大-无穷大这类运算)
浮点数的运算
步骤
十进制为例
1、对阶
对阶就是将阶级数对齐,也就是相同的次方
注意,对齐时,小阶向大阶对齐,也就是把小阶变大,而不是把大阶变小
因为如果把大阶变小的话,就会出现上图那种情况,尾数的整数部分会增加成很多位,这显然不符合尾数的要求
2、3、4:
2、对完阶之后,就可以尾数加减
3、之后,进行规格化,对于二进制浮点数来说,必须让数值位的第一位非零,也就是小数点后第一位
4、之后要进行舍入,因为一般计算机的寄存器有位数限制,如果某个计算结果的位数超出了寄存器的位数,就需要对结果进行舍去,同时要看是否需要进位
上图介绍了三种舍入规则
5、判断溢出
对于二进制来说,阶码也都有一定的位数,当规格化之后的结果,需要用更高位数的阶码来表示时,此时所需的位数超出了阶码的最大位数上限,所以,会发生溢出
上图以十进制举例,要求阶码不能超过两位,最后规格化之后的结果来到了三位,由于已经规格化了,尾数无法变动,而阶码又发生了超出最大位数,所以,发生了溢出
放到二进制也一样,只不过阶码用移码表示,例:2的11次方,当结果为2的111次方时,就发生了溢出(超出了位数就是溢出了,哪怕移码与真值不是直接对应,但是因为只要超出位数就说明当前位肯定已经满足不了当前的结果了,不管是上溢出还是下溢出都是溢出)
二进制
注意
(不一定使用IEEE标准,甚至很少使用,因为位数太多,难以判卷)
0、先进行一步进制转换:
十进制->二进制补码
首先,由上面“浮点数的表示”可以总结出:上面的两个式子都是真值的表示,不过之前都是“尾数采用二进制,阶码采用十进制”来表示真值
这里,我们又做了一步转化,将阶码的十进制也转为二进制
要注意一个小tip:十进制中的分母都是2的进制数,所以,这种类型的真值,分子就会转为“尾数”,而分母则转为阶码(因为直接可以得到2的n次方)
将阶码、尾数都转为补码,并吸纳外带的符号,
注意:上题规定阶码是2位,所以,使用双符号位
同样的原理,将尾数也转为补码,采用双符号位,因为“数符”也是取2位
1、对阶:
圈1:首先要求阶差,该步既可以判断出谁小,同时还可以计算出小多少
这里主要涉及到补码的减法,由一二章前面的内容可知,补码减法,就是X的补码 + Y的相反数的补码(全部位取反之后加一),之后就是使用补码的加法运算的规则
圈2:得到计算结果后,要先转为原码(注意是双符号位),有了原码就可以知道谁大谁小,且差多少
圈3:进行对阶,小的向大的对阶,阶码直接变成与大的那个阶码一样的阶码
之后,尾数要左移或者右移对应的“十进制差值”位
圈4:实际效果如圈4所示
2、尾数加减
圈1、看清楚题目要求X-Y
圈2、而尾数现在都是补码,所以,使用“补码减法”来进行尾数相减
第一步就是将Y的尾数补码,修改为-Y的补码(全部位,包括符号位,按位取反后+1)
圈3、相加之后,使用双符号位判断是否溢出,最高位直接丢弃,最终的结果是双符号异号,所以发生了溢出
圈4、从真值的角度看,尾数真值的绝对值超过了1,所以肯定溢出
3、规格化
圈1:由于上一步发生了溢出,所以这里可以使用“右规”来修复溢出
整体右移一位
圈2:高位补1,因为这里的高位补多少取决于正确的符号应该是多少,而右移之后,原先的符号位仅剩一位,那个就是正确的符号位,所以剩下的那个符号位是啥,这里高位就补啥
圈3:尾数弄完之后,阶码要相应的改变,阶码+1
tips:不管阶码是补码还是移码还是不同偏移量的移码,他们虽然需要一定的转换才能转换为真值,但是他们随着真值的递增,他们的字面量也是+1即可,所以,需要阶码加多少直接加就可以了,加完之后如果答案需要真值,再转为真值
4 舍入
由于规格化时舍去的是一个0,所以无需进行舍入
5 判断溢出
直接看第三步规格化的结果,判断阶码的双符号位是否相同,相同则没有溢出
这一步与第2步第3步判断溢出的侧重点不同,该步侧重检查阶码,2、3侧重检查尾数
补充:有舍入的情况
“0”舍“1”入:
当被丢弃的是0的话,直接舍去
1与2、当被丢弃的是1的话,要在末尾+1
3、如果操作完之后尾数又溢出了,那么再次右规
恒为1法:
补充 判断阶码溢出
发生上溢出,即绝对值超出最大值范围,则计算机会抛出异常
而发生下溢出,计算机会将其视为0
(PS,这一块没有认真讲,可以参考“十进制的这一步”,以及视频中提到不在考试范围的那节课“浮点数的表示范围”)
补充:规避中间过程的舍入
我们在计算时,可能在尾数加减然后规格化时,不止一次的加减和规格化,而如果每次中间过程都进行舍入,就会导致最后结果的误差很大,所以,一般计算机会将尾数的计算过程放在一个比要求的位数还要多一些位数的寄存器内进行计算,这样,对中间过程不做舍入,就保证了计算误差的减小,最后再将结果舍入放回原来位数的寄存器
强制类型转换
常规转换
上图中左边框起来的部分都是默认32位机型
对于64位机型,如果是long转为double,会有精度确实
因为虽然二者都是占用64位,也就是64bit,但是double由于是浮点数,他的尾数只有52位,之后是1位符号,11位阶码
而long的63位(1位符号位)如果都是有效值的话,哪怕double表示的数的范围大,但是他表示的精度只能精确52位有效值,其他由阶码而拓展的位都是0,不是有效值,所以,精度缺失
(相当于double存:1.24 乘 10的5次方,结果是 124000
而long存:123998)虽然double能通过阶码弥补表示的数的范围大小,但是无法弥补精度,也就是有效值不够多
int与float
int的有效值位数是31位
而float的有效值实际上有24位
1、由于int的有效值位数更多,所以int转float,可能会损失精度
2、而由于float有阶码的支持,所以float表示的数的范围更大,所以,float拿一个大数去转为int,可能会造成溢出
3、那为什么float明明有效值位少,转为int还是会损失精度呢
因为当float表示的真值是0.000001的时候,转为int就是0,就会损失精度,也就是浮点数的小数部分会直接被int砍掉,所以会造成精度损失,除非float小数部分都是0,就不会精度损失