ALU(算数运算单元)是CPU上的核心部分。
运算速度
位运算最快,
然后加减法,
最后乘除
现代处理器上 位运算和加减法差距微乎其微了。
时间复杂度的大O表示法
计算机的时间主要花费在计算和存储上,所以赋值语句作为时间复杂度的计量单位
排序方法
选择排序 可视化在P14
O(n2)
归并排序 可视化在P14
O(nlogn)
改良Dijkstra算法
位运算
位运算是用电路最容易实现的运算
加减法
相比于乘除法,成本更低
可以通过软件的方式实现乘除,
但是至少要有加法
真值表
~非
&且
V或
->如果 那么
- 如果 True 那么 (True/False) 根据结果,看是否语句成立;如果 False(无条件命题) 就必定语句成立
- 如果 下雨 那么 地面湿 是 True
- 如果 下雨 那么 地面不湿 是 False
- 如果 不下雨 那么 地面湿 或 不湿 都是 True
<->当且仅当
条件和结果同对同错,才成立
浮点数 IEEE754
1位符号位 8位整数值 31位小数值
NaN (not a number)
INF 最大值
0分为+0和-0,防止计算完边界值正负反转。
精度误差
#define SUM(T,st,ed,d) ({
\
T s = 0; \
for (int i = st; i != ed + d; i += d) \
s += (T) 1 / i; \
s; \
})
#define n 1000000
int main(){
printf("%.16f\n" , SUM(float,1,n,1));
printf("%.16f\n" , SUM(float,n,1,-1));
return 0;
}
输出:
14.3573579788208008
14.3926515579223633
可见正向递增和反向递增,结果不同
这就是精度误差的一种表现
使用double可以降低这种误差
正反数的补码转换
为什么负数的补码可以通过全部位取反末位+1的方式得到正数的补码?
负数的补码可以通过全部位取反末位+1的方式得到正数的补码,是因为这样可以使得两个数的和等于零。例如,-9的原码是1000 1001,取反后是0111 0110,加1后是0111 0111,这就是-9的补码。而9的原码和补码都是0000 1001,两者相加就是1000 0000,也就是零。这样可以方便计算机进行加减运算。
对于无符号数,它的相反数和补码取法一样,也是先对所有位取反,再+1
因为这样做得到的新数和原数相加得全0,通过高位溢出,可以达到相反数的效果
与或非 的电路及应用
晶体管中的与或非 (美版)
里面是三根电线:两个电极一个控制线
当控制线通电,电流从一侧电极流通,通过晶体管到达另一端电极
当输入为1的时候,连通接地的电路,导致输出0
异或门的设计
非(A与B) 与 (A或B)
半加器
全加器
三个数相加
两个8位数加法器
ALU的Flag
负数 flag
0 flag
溢出 flag
存储电路
与或锁存器。有一个Set输入和一个Reset输入。
寄存器
用锁存器矩阵,节约连线
计算机组成原理中的与或非
异或门的设计
(非A与B) 或 (A与非B)
同或就是异或的取反
加法器
我思考,可以把每一位的异或运算作为公共值,共同使用
其实就是说,在第一次计算的时候把Gi Pi的值传给后面所有的加法器,就让他们向下计算一步
原本的Ci计算深度是3
假设,以C4来看,串行进位的并行加法器,C4计算深度为3(所有位的Ci恒为3)
采用优化并行进位的方法,G3 P3传递给FA4加法器的时候,其他都已经提前算完了,只需要计算 G3和P3相关的几步(G3的一次与和P3的三次与)
计算深度变成4了?
佛系吧,看来这里讲得不好
换了一个课程
这个是说,把C的计算改成不依赖于前面的C,相当于每个电路都要做完全独立的计算,有计算重复。
仔细想想,C4的计算不还是要把之前的C1C2C3的计算复杂度全计算一遍吗。
看到图我明白了,这里计算复杂度其实是增加了(原本C1C2C3C4都可以复用之前的计算结果,现在C2C3C4都要自己独立算一遍前面的C),但是由于变成了三级门电路,可以多个门同时工作,导致花费的时间减少,只是花的电量增加了(有更多的电子元件加入进来)
加法器提供溢出位检测flag
补码加减运算器
补码加减运算和无符号数加减运算都可以用这套电路,但是判断溢出的方式不同
标志位
OF标志位
无符号数加减运算,OF为1,并不能说明无符号数发生了溢出
只对有符号数有效
OF=最高位产生的进位 异或 次高位产生的进位
因为在有符号数运算中,次高位进位会导致符号位变化。最高位进位会导致溢出。
我的思考:
为什么最高位和次高位同时有进位,不算溢出?
因为7和-7相加,会导致最高位和次高位同时有进位,最终结果为0。
SF标志位
SF为0,结果符号为正
SF为1,结果符号为负
ZF标志位
ZF为1,结果全0
CF标志位
CF=最高位产生的进位 异或 sub(加法sub=0,减法sub=1)
CF为1,表示无符号数的加法向最高位进位了,有溢出
我的思考:
为什么最高位有进位和sub(cin)为1,不算溢出?
因为7和7相减,其中减数7需要换成7的相反数(实际上是负数对容量的最大数+1取模后的结果,导致两个数相加后模0),会导致最高位和次高位同时有进位,最终结果为0。
我的思考:
为什么最高位没进位,sub(cin)为1,算溢出?
因为这时被减数小于减数,结果为负数,超越了数的表示范围。
比如4位数最大为15,6 + ((-7) % 15 + 1) = 6 + 9 = 15 < 15 所以没进位
进制
算数移位
运算器的组成
原码一位乘法
乘积其实是用了两倍的空间,
比如8位乘8位,结果是16位
这16位的得到,是通过被乘数移位相加
比如01111111(被乘数)乘01000001(乘数)
就是1被乘数 + 101111111000000(被乘数左移6位)
等于
0,000,000,001,111,111 // 127
+ 0,001,111,111,000,000 // 127 * 64
= 0,010,000,000,111,111 // 127 * 65
- ACC置零
被乘数(绝对值)放到ALU的X寄存器
乘数(绝对值)放到MQ - 取MQ最低位的值判断,
如果是1,把ALU的值放到ACC里
如果是0,跳过 - ACC和MQ的值全部右移一位,
ACC的溢出放到MQ的高位,
MQ的溢出丢弃(因为这时MQ的最低位已经在第二步使用完毕了,没有价值) - 重复2,取MQ最低位的值判断,
如果是1,把ALU的值和ACC相加放到ACC里
如果是0,跳过 - 重复3
- 符号位:取乘数和被乘数的符号做异或运算
为什么叫原码一位乘法?
因为每次有一个位参与运算,速度较慢
还有一种更快的,叫原码二位乘法,同时计算两个位
补码一位乘法
手算除法
恢复余数法
初始想法
- 比较ACC中的被除数和通用寄存器的除数,谁更大
如果ACC比通用寄存器大,商1
如果ACC比通用寄存器小,商0 - 如果商为1,把被除数减去除数,
实际上就是加上除数相反数的补码
实际做法
由于ALU不会比较两个数的大小
所以把比较的步骤,改为默认商1
-
默认商为1,把被除数减去除数,
实际上就是加上除数相反数的补码 -
如果得到的余数符号位发生了变化,变为了1(被除数和除数一开始都是取的绝对值)
就是商错了,把商恢复到0,
再把ACC的值恢复到计算前的值,也就是加上除数的补码,就可以恢复 -
把ACC和MQ左移1位
加减交替法
我已经自己证过一遍下面这个图
最后一步还是需要恢复余数
注意:被除数要小于除数,因为定点小数无法表示大于1的范围
补码的加减交替法
不同于原码的加减交替法
这里的减数和被减数都是带符号运算的
- 同号,被除数减除数;异号,被除数加除数
- 运算完一次整体左移
- 最后一位商直接设为1,误差产生
C语言
强制类型转换的原理
short 和 unsigned short只是解析方式不一样,内存中的值都一样
短整数变长整数,符号扩展,
有符号数的负数需要前面添加全1
无符号数前面添加全0
数据存储结构
大小端模式
编址与寻址
编址是按字节编址。寻址可以按字、半字、字节寻址
每次访存只能读/写一个字
浮点数表示