|
对于处理可读数字,经常会采用二进制编码的十进制 (Binary Coded Decimal, BCD)格式,而在处理器中可以快速的处理这种格式。虽然在许多高级 BCD 处理操作位于 FPU 中,但核心处理器包含一些简化的指令,用于使用 BCD 值执行运算。
1、不打包 BCD 运算
不打包 BCD 是用一个字节表示单个十进制数(0-9中的一个数字)。IA-32 平台提供了专门的指令用于从一般数学操作生成不打包 BCD 值。
用于把二进制运算结果转换为不打包 BCD 格式的指令有 4 条:
这些指令必须和一般的无符号整数指令 ADD、ADC、SUB、SBB、MUL 和 DIV 组合在一起使用。
AAA、AAS 和 AAM 指令在它们各自的操作数之后使用,把二进制结果转换为不打包 BCD 格式。
AAD 指令有些不同,在 DIV 指令之前使用,用于准备被除数以便生成不打包 BCD 结果。
上面 4 个指令都使用一个隐含的操作数 -- AL 寄存器。AAA、AAS 和 AAM 指令假设前一个操作数的结果放在 AL 寄存器中,并且把这个值转换为不打包 BCD 格式。ADD 指令假设被除数以不打包 BCD 格式村放在 AX 寄存器中,并且把它转换为 DIV 指令要处理的二进制格式,结果是正确的一个不打包 BCD 值、AL 寄存器中的商和 AH 寄存器中的余数 (按照不打包 BCD 格式)。
当处理多字节的不打包 BCD 值时,必须使用进位和溢出标志才能确保计算的正确。下面程序演示加法运算中的 AAA 指令的使用:
.section .data
value1:
.byte 0x05, 0x02, 0x01, 0x08, 0x02
value2:
.byte 0x03, 0x03, 0x09, 0x02, 0x05
.section .bss
.lcomm sum, 6
.section .text
.global _start
_start:
nop
xor %edi, %edi
movl $5, %ecx
clc
loop1:
movb value1(, %edi, 1), %al #依次取得各位上的值
adcb value2(, %edi, 1), %al
aaa #调整相加后的值为不打包BCD值
movb %al, sum(, %edi, 1) #存储一次相加的结果
inc %edi #内存指针移位
loop loop1 #循环一次 %ecx 中的值减 1
adcb $0, sum(, %edi, 4) #当最高位相加时不忘加上进位值
movl $1, %eax
movl $0, %ebx
int $0x80 说明:
不打包 BCD 值用小端格式存储。
程序中 ADC 指令是为了确保加上前一次家法操作产生的任何进位位(在使用 ADC 之前要记得使用 CLC 指令确保进位位被清空)。
使用 gdb 跟踪程序发现,在 9+1 时,寄存器中的值为 0x0a;在执行 aaa 后,用 info reg eax 查看寄存器中的内容:(gdb) info reg
eax 0x100 256 上面的结果中,AH 寄存器中的不打包值为 1,AL 寄存器中为0,与此同时 CF 位也会被置位(aaa调整逢十进一认为进位),这个 CF 位会被在下一次的 adcb 指令中添加进来。这里,AH 中的值会在下一次的计算中用到(如果下一次计算还有大于10而进位的,这个进位的信息会被加到 AH 中来),但对于实际所需要的运算结果不产生影响。
AAD 指令和上面其它 3 种指令有些不同,它是在 DIV 指令之前使用,用于准备被除数以便生成不打包 BCD 结果。AAD 指令假设被除数以不打包 BCD 格式存放在 AX 寄存器中,并且把它转换为 DIV 指令要处理的二进制格式。结果是一个不打包 BCD 值(AL 寄存器中为商,AH 寄存器中为余数)。如执行指令 movw $0x108, %ax,然后再执行 aad 指令,那么 EAX 寄存器中的值为 0x12,即十进制的 18。
AAD 指令使用示例:
.section .data
value1:
.byte 0x02, 0x08, 0x01, 0x02, 0x07 #被除数数组
divisor:
.byte 3 #除数
.section .bss
.lcomm quotient, 5 #存放商
.lcomm remainder, 1 #存放余数
.section .text
.global _start
_start:
nop
xor %edi, %edi
movl $5, %ecx #5位被除数的商最多5个(循环5次)
loop1:
movb value1(, %edi, 1), %al
aad #调整不打包BCD到正常的二进制数
divb divisor # AH中为余数,AL中为商
cmpb $0, %al #不够除,调整
je adjust
movb %al, quotient(, %edi, 1) #保存商
inc %edi
loop loop1
movb %ah, remainder #除完,保存最后的余数
movl $1, %eax
movl $0, %ebx
int $0x80
adjust:
inc %edi #不够除,再移1位
movb value1(, %edi, 1), %al
aad
divb divisor
movb %al, quotient(, %edi, 1) #保存商
dec %ecx #循环次数-1
inc %edi #移动被除数数组指针
jmp loop1 用 GDB 调试,在 movl $1, %eax 处下断点,直接运行到此,然后看 quotient 和 remainder 两处内存的值:(gdb) x/5b "ient
0x80490d0 <quotient>: 0x00 0x09 0x03 0x07 0x05
(gdb) x/5b &remainder
0x80490d5 <remainder>: 0x02 0x00 0x00 0x00 0x00 由调试信息可见,商按照大端格式存储,其值为 9375 ,余数为 2 。 |
|