计组与微机控制
7.6 BCD调整和逻辑运算
7.6 BCD调整和逻辑运算
本节讲符号扩展、BCD 调整、位操作、移位/循环移位、串操作
因为它们都和“标志位、数据长度、隐含寄存器”有关。
一、DIV / IDIV 使用时的注意点
前面我们已经讲过:
DIV src IDIV src
其中:DIV :无符号除法 IDIV :带符号除法
它们都只有一个显式操作数,这个操作数是除数。被除数是隐含的。
1. 除法后,标志位没有意义
书上第一点说:DIV / IDIV 对标志寄存器的标志位无定义
意思是:执行除法后,不要用 CF、ZF、SF、OF 等标志位来判断除法结果。
比如:
DIV BL JZ SOMEWHERE
这种写法在逻辑上不可靠,因为 DIV 后 ZF 是不确定的。
除法结果应该直接看:AL / AH AX / DX 而不是看 FLAGS。
2. 被除数长度必须是除数长度的两倍
这个非常关键。8 位除法
如果除数是 8 位:DIV BL
那么被除数必须是 16 位,固定放在:AX
结果是:AL = 商 AH = 余数
16 位除法 如果除数是 16 位:DIV BX
那么被除数必须是 32 位,固定放在:DX:AX
结果是:AX = 商 DX = 余数
所以要记住一句话:除数 8 位,被除数 AX 除数 16 位,被除数 DX:AX
3. 无符号除法前,高位要清零
例如要做:AX ÷ BX
但 DIV BX 实际做的是:DX:AX ÷ BX
所以如果 DX 里面有乱七八糟的旧数据,就会把被除数变大,结果就错了。
因此无符号 16 位除法前,经常要写:MOV DX, 0 DIV BX
或者:XOR DX, DX DIV BX
其中:XOR DX, DX
效果是把 DX 清 0,而且速度通常比 MOV DX,0 快。
4. 带符号除法前,要做符号扩展
如果是 IDIV,不能简单把高位清零,因为负数的高位应该扩展为 1。
例如 AX 是一个 16 位带符号数,要用 IDIV 做 32 位被除数除以 16 位除数,那么要先把 AX 符号扩展到 DX:AX。
这就是 CWD 的作用。
如果是 8 位带符号除法,被除数要在 AX 中,而原数据在 AL 中,就要用 CBW 把 AL 扩展成 AX。
总结:8 位带符号除法前:用 CBW 16 位带符号除法前:用 CWD
二、除法溢出问题
除法和乘法不同。乘法如果结果太长,可以用 AX 或 DX:AX 保存。
但是除法有一个问题:商的位置是固定的。
8 位除法:商必须放得进 AL
也就是商不能超过 8 位范围。
16 位除法:商必须放得进 AX
也就是商不能超过 16 位范围。
如果商放不下,就会发生除法溢出,CPU 会产生除法错误中断。
1. DIV 的溢出
对于无符号除法:8 位除法时:AX ÷ src8
商要放进 AL,所以商必须:0 ~ 255
如果商大于 FFH,就溢出。
16 位除法时:DX:AX ÷ src16
商要放进 AX,所以商必须:0 ~ 65535
如果商大于 FFFFH,就溢出。
2. IDIV 的溢出
对于带符号除法:8 位除法的商放进 AL,范围是:-128 ~ +127
书上写的是:-127 ~ +127
但从补码 8 位带符号范围看,完整范围应为:-128 ~ +127
16 位除法商放进 AX,范围是:-32768 ~ +32767
如果商超过这个范围,就会除法溢出。
3. IDIV 余数的符号
IDIV 的余数符号总是与被除数相同
例如:-7 ÷ 3 = -2 余 -1
因为:-7 = 3 × (-2) + (-1)
余数和被除数 -7 一样,都是负号。
三、CBW 和 CWD:符号扩展指令
接下来书上讲:CBW CWD
这两个都是符号扩展指令。
1. CBW CBW 是:Convert Byte to Word
也就是:把字节扩展成字
格式:CBW 功能:AL 的符号扩展到 AH
如果 AL 是正数,也就是最高位为 0:AH = 00H
如果 AL 是负数,也就是最高位为 1:AH = FFH
所以:AL < 80H → AH = 00H AL ≥ 80H → AH = FFH
注意,这里的比较是看二进制最高位,不是普通十进制大小。
举例 1:AL 是正数 AL = 34H
二进制最高位是 0,所以执行:CBW
之后:AX = 0034H
举例 2:AL 是负数 AL = F4H
F4H 作为 8 位补码表示负数,因为最高位是 1。
执行:CBW 之后:AX = FFF4H
这表示把 8 位负数 F4H 扩展成 16 位负数 FFF4H。
2. CWD
CWD 是:Convert Word to Doubleword
也就是:把字扩展成双字,格式:CWD
功能:AX 的符号扩展到 DX,如果 AX 是正数:DX = 0000H
如果 AX 是负数:DX = FFFFH
也就是:AX < 8000H → DX = 0000H AX ≥ 8000H → DX = FFFFH
举例 1:AX 是正数
AX = 1234H 执行:CWD
之后:DX:AX = 0000:1234H
举例 2:AX 是负数
AX = FFF4H执行:CWD
之后:DX:AX = FFFF:FFF4H
3. CBW/CWD 主要用在哪里?
主要用于 IDIV 前面。例如做 16 位带符号除法:
MOV AX, 被除数 CWD IDIV BX
CWD 的作用就是把 AX 变成正确的 32 位带符号被除数 DX:AX。
四、例 4.12:带符号除法 0BF4H ÷ 0123H
书上例子大意是完成:0BF4H ÷ 0123H
程序:
MOV AX, 0BF4H CWD MOV BX, 123H IDIV BX
我们一步一步看。
1. 装入被除数
MOV AX, 0BF4H此时:AX = 0BF4H
0BF4H 最高位是 0,所以这是正数。
2. 做符号扩展
CWD 因为 AX 是正数,所以:DX = 0000H
于是:DX:AX = 0000:0BF4H这就是 32 位被除数。
3. 装入除数
MOV BX, 123H
即:BX = 0123H
4. 执行带符号除法
IDIV BX含义是:DX:AX ÷ BX
也就是:00000BF4H ÷ 0123H
计算结果:商 = 000BH 余数 = 00F4H
所以:AX = 000BH DX = 00F4H
五、BCD 码是什么
接下来进入十进制调整指令,这部分初学会觉得奇怪,因为现在很少直接用 BCD 码了,但 8086 早期很重视十进制计算。(之前提过一部分)
BCD 是:Binary Coded Decimal 二进制编码的十进制数
它不是普通二进制数,而是用二进制形式表示十进制数字。
1. 压缩 BCD 码
压缩 BCD 的特点是:4 位二进制表示 1 位十进制数字
例如十进制:3569
每一位分别编码:3 → 0011 5 → 0101 6 → 0110 9 → 1001
所以 3569 的压缩 BCD 是:0011 0101 0110 1001B
也就是:3569H
注意,这个 3569H 不是普通二进制数意义上的十六进制 3569,而是每 4 位表示一个十进制数字。
2. 非压缩 BCD 码
非压缩 BCD 的特点是:一个字节表示一个十进制数字 低 4 位是 BCD 码 高 4 位通常为 0
例如 3569 的非压缩 BCD 是:03H 05H 06H 09H
写成二进制就是:0000 0011 0000 0101 0000 0110 0000 1001
3. 为什么需要 BCD 调整?
因为 CPU 的 ADD、SUB、MUL、DIV 本质上都是二进制运算。
但是 BCD 数要求每一位只能是:0000 ~ 1001也就是 0 ~ 9
如果二进制运算后某一位超过 9,就不是合法 BCD 码了,需要调整。
例如:5 + 8 = 13
如果用 4 位 BCD 加法:0101 + 1000 = 1101
1101 是十进制 13,但在 BCD 中 1101 不是合法数字,因为 BCD 单位只能到 1001。
所以要调整成:0001 0011
也就是十进制 13 的 BCD 表示。
六、十进制调整指令总览
8086 的 BCD 调整指令有:
AAA AAS DAA DAS AAM AAD
它们的含义:
AAA:非压缩 BCD 加法调整 AAS:非压缩 BCD 减法调整 DAA:压缩 BCD 加法调整 DAS:压缩 BCD 减法调整 AAM:非压缩 BCD 乘法调整 AAD:非压缩 BCD 除法调整
七、AAA / AAS:非压缩 BCD 加减调整
AAA 和 AAS 用于非压缩 BCD。
格式:
AAA AAS
它们一般跟在:
ADD / ADC SUB / SBB 后面使用。
1. AAA 的作用
AAA 用于:非压缩 BCD 加法后调整
例如:05H + 08H = 0DH
0DH 不是合法非压缩 BCD 数字,因为低 4 位 D 大于 9。
AAA 会把结果调整成:AH = AH + 1 AL = 03H
表示十进制 13,即:高位 1,低位 3
2. AAS 的作用
AAS 用于:非压缩 BCD 减法后调整
比如个位减法不够减时,它会调整 AL,并通过 AH 反映借位。
3. AAA/AAS 调整规则
书上写的是:如果:AL 的低 4 位 > 9 或者 AF = 1那么就调整。
AAA 是加法调整:
AL ← AL + 6 AH ← AH + 1 AF ← 1 CF ← 1 AL ← AL & 0FH
AAS 是减法调整:
AL ← AL - 6 AH ← AH - 1 AF ← 1 CF ← 1 AL ← AL & 0FH
如果不需要调整,则一般:AF = 0 CF = 0 AL 低 4 位保留
重点记住:AAA/AAS 用于非压缩 BCD 结果主要在 AH 和 AL 中
八、DAA / DAS:压缩 BCD 加减调整
DAA 和 DAS 用于压缩 BCD。
格式:
DAA DAS
一般跟在:
ADD / ADC SUB / SBB 后面。
1. DAA 的作用
DAA 是:Decimal Adjust after Addition用于压缩 BCD 加法后调整。
例如:
MOV AL, 25H ADD AL, 37H DAA
先普通二进制加法:25H + 37H = 5CH
但是 BCD 上应该是:25 + 37 = 62
BCD 应该是:62H
所以 DAA 会把 5CH 调整成 62H。
2. DAS 的作用
DAS 是:Decimal Adjust after Subtraction用于压缩 BCD 减法后调整。
例如:52 - 19 = 33用普通二进制减法后可能需要调整,DAS 会把结果修正成合法压缩 BCD。
3. DAA/DAS 和 AAA/AAS 的区别
这个非常重要:
AAA/AAS:非压缩 BCD,结果涉及 AH 和 AL DAA/DAS:压缩 BCD,只调整 AL,不改变 AH
也就是说,DAA/DAS 一次调整的是 AL 里面的两个 BCD 数字。
例如:AL = 59H它表示压缩 BCD:十位 5,个位 9
九、AAM / AAD:非压缩 BCD 乘除调整
这一页说 8086/8088 只提供了非压缩 BCD 的乘除调整指令。
也就是:
AAM AAD
注意:
没有压缩 BCD 的乘除调整指令
如果要做压缩 BCD 乘除,一般要先转换成非压缩 BCD。
1. AAM:乘法后调整
AAM 用在 MUL 后。格式:AAM
作用:把 AL 中的乘积调整成非压缩 BCD
调整规则:AH ← AL / 10 的商 AL ← AL / 10 的余数
因为书上写 0AH,其实就是十进制 10。
例 4.14:7 × 9
程序:
MOV AL, 07H MOV BL, 09H MUL BL AAM
先看 MUL:
AL = 07H BL = 09H
执行:MUL BL
结果:AX = 003FH
因为:7 × 9 = 63 63 的十六进制是 3FH
此时 AL = 3FH,也就是十进制 63。
然后执行:AAM
AAM 做的是:
AH = 63 / 10 = 6 AL = 63 mod 10 = 3
所以执行后:AH = 06H AL = 03H
也就是非压缩 BCD 的 63。
2. AAD:除法前调整
AAD 用在 DIV 前。格式:AAD
作用:把 AH:AL 中的非压缩 BCD 转成普通二进制数,放入 ALAH 清 0
调整规则:AL ← AH × 10 + AL , AH ← 0
例 4.15:73 ÷ 2
程序:
MOV AX, 0703H MOV BL, 02H AAD DIV BL AAM
先看:AX = 0703H
也就是:AH = 07H AL = 03H表示非压缩 BCD 的 73。
执行:AAD
计算:AL = AH × 10 + AL = 7 × 10 + 3 = 73 = 49H AH = 00H
所以:AX = 0049H
然后:DIV BL即:73 ÷ 2
结果:商 = 36,余数 = 1
所以:AL = 24H AH = 01H
注意 24H 是普通二进制的 36,不是 BCD 24。
然后:AAM把 AL 中的 36 调整成非压缩 BCD:
AH = 36 / 10 = 3 , AL = 36 mod 10 = 6
于是:AH = 03H,AL = 06H 表示商 36。
但是书上也提醒:余数丢失了。
因为 DIV 后余数原本在 AH 中,但 AAM 又改写了 AH,所以余数被覆盖。
如果要保留余数,需要在 AAM 前先保存 AH。
例如:
DIV BL MOV CL, AH AAM
这样 CL 中保存余数。
十、BCD 调整部分总结
你可以这样记:
AAA / AAS:非压缩 BCD 加减,跟 ADD/SUB 后面 DAA / DAS:压缩 BCD 加减,跟 ADD/SUB 后面 AAM:非压缩 BCD 乘法后调整,跟 MUL 后面 AAD:非压缩 BCD 除法前调整,放在 DIV 前面
特别注意:
AAM 是乘法之后 AAD 是除法之前
这个顺序很容易考。
十二、4.3.3 位操作类指令
接下来进入位操作类指令。位操作类指令分三类:
逻辑运算指令 移位指令 循环移位指令
先讲逻辑运算。
十三、逻辑运算指令
包括:AND OR XOR NOT TEST 它们都是按位操作。
1. AND:按位与
格式:AND dest, src
功能:dest ← dest AND src
按位与规则:1 AND 1 = 1 其他情况 = 0
所以 AND 常用于:清某些位 保留某些位 提取某些位
举例:清低 4 位
AND AL, 0F0H假设:
AL = 5BH = 0101 1011B
0F0H 是:1111 0000B
执行后:AL = 0101 0000B = 50H
低 4 位被清 0,高 4 位保留。
举例:取低 4 位
AND AL, 0FH
0FH 是:0000 1111B
执行后只保留低 4 位。
2. OR:按位或
格式:OR dest, src
功能:dest ← dest OR src
按位或规则:有 1 就是 1
OR 常用于:把某些位置 1
举例:把最高位置 1
OR AL, 80H
80H 是:1000 0000B
执行后 AL 的最高位一定变成 1。
3. XOR:按位异或
格式:XOR dest, src
功能:dest ← dest XOR src
异或规则:相同为 0 不同为 1
XOR 常用于:翻转某些位,清零
举例:清零 XOR AX, AX
因为任何数和自己异或都是 0:AX = 0000H
这是汇编里很常见的清零写法。
举例:翻转低 4 位
XOR AL, 0FH
低 4 位会全部取反,高 4 位不变。
4. NOT:按位取反
格式:NOT dest
功能:dest ← NOT dest
也就是每一位取反:0 变 1,1 变 0
例如:AL = 55H = 0101 0101B
执行:NOT AL之后:
AL = AAH = 1010 1010B
注意:NOT 不影响任何标志位。
5. TEST:测试指令
格式:TEST dest, src
功能:临时计算 dest AND src 但结果不保存 只影响标志位
它很像 CMP。
区别是:CMP:临时做减法,用来比较大小,TEST:临时做 AND,用来测试某些位
举例:判断 AL 的最高位是否为 1
TEST AL, 80H JNZ SIGN_IS_1
因为 80H 只有最高位为 1。
如果 AL 最高位为 1,则:
AL AND 80H ≠ 0 ZF = 0
所以 JNZ 跳转。
如果 AL 最高位为 0,则:
AL AND 80H = 0 ZF = 1
JNZ 不跳。
6. 逻辑运算对标志位的影响
除了 NOT 以外,AND、OR、XOR、TEST 都会影响标志位。
主要规律:
CF = 0 OF = 0 ZF、SF、PF 根据结果设置 AF 不确定
NOT:不影响任何标志位 这点要记。
十四、例 4.16:逻辑表达式实现
书上例子是实现:
LOGIC = NOT(LOG1 AND LOG2) XOR (LOG3 AND LOG4)
程序大意:
MOV AL, LOG1 AND AL, LOG2 NOT AL MOV AH, LOG3 AND AH, LOG4 XOR AL, AH MOV LOGIC, AL
我们逐句看。
1. 先算 LOG1 AND LOG2
MOV AL, LOG1 AND AL, LOG2
这两句后:
AL = LOG1 AND LOG2
2. 再取反
NOT AL之后:
AL = NOT(LOG1 AND LOG2)
3. 计算 LOG3 AND LOG4
MOV AH, LOG3 AND AH, LOG4
之后:
AH = LOG3 AND LOG4
4. 两部分异或
XOR AL, AH
之后:AL = NOT(LOG1 AND LOG2) XOR (LOG3 AND LOG4)
5. 保存结果
MOV LOGIC, AL最终把结果存到 LOGIC。
这个例子主要告诉你:复杂逻辑表达式可以拆成一步一步的位运算。
