计算机算术运算
有关计算机的算术运算,也是一个老生常谈的话题了。本章节的关注点主要落在算术运算的溢出、高级算术运算、算术运算的硬件部件。
加法和减法可能溢出
对加法而言:
- 正负操作数相加, 不溢出
- 两个正操作数相加
符号位为1,表示发生溢出 - 两个负操作数相加
符号位为0,表示发生溢出
对减法而言:
- 两个相同符号位的操作数相减,不溢出
- 负操作数减去正操作数
符号位为0,表示发生溢出 - 正操作数减去负操作数
符号位为1,表示发生溢出
对溢出的处理:
- 有些高级语言(例如, C) 忽略溢出部分
- 其它语言 (例如, Ada, Fortran) 需要引发异常
当触发异常时,需要调用相应的处理程序,对MIPS而言,操作如下:
- 保存PC指针到EPC(exception program counter) 寄存器
- 跳转到预定义的异常处理地址
- mfc0 (move from coprocessor reg) 指令可以取回EPC 值, 恢复PC指针
乘法
- 手工模拟二进制乘法:
二进制乘法非常简单,几乎就是看位说话。不难归纳出其计算算法为:
- 令积初始值为 0
- 若乘数末位为 1,则积加上当前被乘数;若乘数末位为0,则什么都不干
- 乘数右移 1 位,被乘数左移 1 位
- 乘数是否已经为0,是则退出;否则返回第 2 步
由此,我们可以设计出一套乘法器:
Multiplicand是被乘数,Multiplier是乘数,被乘数寄存器是64位的,而乘数寄存器是32位的。乘法通过64位的ALU进行,最终储存结果到64位寄存器中。其硬件数据流图为:
数一下上面乘法器的使用了多少位空间?一共是64+64+64+32=224位,我们想进一步缩小其所占用的位空间。优化后的硬件如下:
其中,最开始的时候,乘数是放在积寄存器的右半部分。这种方式,我们把被乘数寄存器省去了32位,ALU也省去了32位,省掉了整个64位的乘数寄存器,最终只用到了128位,比原乘法器减少了96位。
在MIPS指令集中,提供了对乘法的支持:
- mul rd,rs,rt # 乘积的低32位存入rd寄存器
- mult rs,rt # 将64-bit乘积存放到HI/LO
- mfhi rd | mflo rd # 传送HI/LO到rd寄存器,对于32位的乘法运算,可以利用HI的值判断乘法是否溢出
除法
- 手工模拟除法:
人类在做除法,就是不断试商的过程。有些计算机中采用类似的算法进行处理——恢复余数法,也即直接做减法,如果得到的余数小于0再把除数加回去。归纳一下恢复余数法的算法流程: - 令被除数-除数
- 若结果大于0,上商1,商左移一位,除数右移一位
- 若结果小于0,上商0,恢复余数,商左移一位
- 重复上述操作,直至商的精度满足要求为止
除法器
最开始64位Remainder(余数寄存器)存放被除数,64位存放除数,用到了64位的ALU,商存放在32位寄存器中,总共224位,其数据流图为:
同样的,还是可以对除法器的位空间做优化:
上面这个图,和乘法器图是一样的(除了控制单元要有所修改之外)。
MIPS也提供了对除法的支持:
- div rs, rt | divu rs, rt # HI存放32位余数,LO存放32位商
- mfhi,mflo指令可访问相应的结果
浮点运算
IEEE 754 标准浮点数
对于规格化浮点数来说:x = (-1)s * (1+尾数) * 2 阶码-偏移
对于单精度浮点数来说,移码为127;双精度浮点数,移码为1023.
阶码为全0和全1的情况保留。
浮点数加法
浮点数加法并不是像简单加法那样,直接送入ALU运算就完事了,实际上,在进行浮点数加法运算之前,我们还需要做一些前置工作。
以1.0002 × 2–1 + (–1.1102) × 2–2为例我,我们需要做的事有:
- 对阶:把小阶的值调整到和大阶一致,即1.0002 × 2–1 + 1.11102 × 2–1
- 尾数相加:1.0002 × 2–1 + 1.11102 × 2–1 = 0.001 × 2–1
- 结果规格化或检查结果是否溢出:0.001 × 2–1 = 1.000 × 2–4
- 舍入:例子无需舍入