做数学

做数学

所有计算都在寄存器之间进行。算术指令先指定目标,然后是两个它读取的源: 表示 dest = src1 + src2。

算术运算
t2, t0, t1←add
t0 = 7t1 = 5+t2 = 12

将位置 t0 和 t1 添加到 t2 中。

从左侧进入的两条线是 t0 和 t1,始终按这个顺序。无论正在运行的是加法、减法、乘法、除法还是取余,它们都以相同的形状进入。两个值进入,执行一次运算,一个结果从右侧输出到 t2。 先指定 t2,因为结果就落在那里,这与本课中每条算术指令都遵循的唯一排序规则一致。

加法和减法

取两个寄存器中保存的数字,并把它们的和存入第三个寄存器。 对减法做同样的事: 计算 t0 - t1。两条指令都只从寄存器读取。

注册算术
add t2, t0, t1 # t2 = t0 + t1
sub t2, t0, t1 # t2 = t0 - t1

需要知道一件事:一个寄存器只能保存 32 位,所以存在上限。如果结果太大而放不下,不会引发错误。该值会悄悄地回绕成非常小的数字,执行继续。日常的值不会达到这个限制,但这个上限值得了解。

要用固定数字代替第二个寄存器,就加上字母 i,它代表立即数: 加上常量 1。没有 subi,所以要减去一个常量,就加上一个负数:。

💡
加的是一个常量。 加的是两个寄存器。混淆这两者是最常见的初学者错误。

乘法和除法

把两个寄存器相乘。 把它们作为整数相除并丢弃小数部分,所以 7 / 2 得到 3。 给出余数,所以 7 % 2 得到 1。除法永远不会导致崩溃。除以零返回 -1。

乘法、除法、余数
mul t2, t0, t1 # t2 = t0 * t1
div t2, t0, t1 # t2 = t0 / t1 (整数)
rem t2, t0, t1 # t2 = t0 % t1 (余数)

整合

将两个数字相加并输出结果
li t0, 7
li t1, 5
add t1, t0, t1 # t1 = 7 + 5 = 12
mv a0, t1 # 打印服务读取 a0
li a7, 1
ecall
li a7, 10
ecall

把 7 加载到 t0。 把 5 加载到 t1。 把它们相加并把 12 写入 t1。 把结果复制到 a0,因为 print 服务总是从 a0 读取它的值。然后 选择 print-integer 服务, 运行它。最后, 和 结束程序。

附加内容:利用移位进行乘法运算

当因数是 2 的幂时,存在比 更快的方法。(逻辑左移)把寄存器的位向左移动,每移动一个位置都会使该值翻倍:移 1 位是乘以 2,移 2 位是乘以 4,移 3 位是乘以 8。移位的开销比乘法小,所以编译器经常使用它。

使用位移进行8的乘法运算
li t0, 5 # 从 5 开始
# 左移 3 位 (5 * 2^3 = 5 * 8)
sll a0, t0, 3 # a0 = 40
li a7, 1 # 打印整数
ecall
li a7, 10
ecall

把 5 放入 t0。 把它的位向左移动三个位置。每个位置都使该值翻倍,所以 5 翻倍三次(5、10、20、40),在 a0 中得到 40。其余部分沿用之前的 print 模式。试着把 3 改成 1 或 2,并在运行前预测结果。

通过移位相乘
5sll a0, t0, 0
×2 →
10sll a0, t0, 1
×2 →
20sll a0, t0, 2
×2 →
40sll a0, t0, 3
0
0
0
0
0
1
0
1

5 左移 0 位仍为 5。

这八个方框是它们上方数字的二进制形式,每个一位,照常从左到右读取。把整行向左移动一个位置,与乘以 2 是同一个操作,因为在任何进制中,把一个数字向左移动一位都会把它的值乘以该进制。二进制只有两个数字,所以乘数是 2。移动三次会复合为乘以 8,这与 对数字 5 所做的相符。