曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 5976|回复: 1
打印 上一主题 下一主题

十进制运算(打包与不打包BCD运算)

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2010-2-18 01:30:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
对于处理可读数字,经常会采用二进制编码的十进制 (Binary Coded Decimal, BCD)格式,而在处理器中可以快速的处理这种格式。虽然在许多高级 BCD 处理操作位于 FPU 中,但核心处理器包含一些简化的指令,用于使用 BCD 值执行运算。

1、不打包 BCD 运算
不打包 BCD 是用一个字节表示单个十进制数(0-9中的一个数字)。IA-32 平台提供了专门的指令用于从一般数学操作生成不打包 BCD 值。

用于把二进制运算结果转换为不打包 BCD 格式的指令有 4 条:
      AAA  :  调整加法操作的结果。
      AAS  :  调整减法操作的结果。
      AAM  :  调整乘法操作的结果。
      AAD  :  准备除法操作的被除数。
这些指令必须和一般的无符号整数指令 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 &quotient
0x80490d0 <quotient>:    0x00    0x09    0x03    0x07    0x05
(gdb) x/5b &remainder
0x80490d5 <remainder>:    0x02    0x00    0x00    0x00    0x00
由调试信息可见,商按照大端格式存储,其值为 9375 ,余数为 2 。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
沙发
 楼主| 发表于 2010-2-26 00:10:45 | 只看该作者

打包 BCD 运算

处理打包 BCD 值时,可用的指令只有 2 条:
      DAA : 调整 ADD 或者 ADC 指令的结果
      DAS : 调整 SUB 或者 SBB 指令的结果
这些指令执行的功能和 AAA 以及 AAS 指令相同,但是操作对象是打包 BCD 值。它们也使用位于 AL 寄存器中隐含的操作数,并且把转换结果存放在 AL 寄存器中,把进位位存放在 AH 寄存器中和辅助进位标志中。

下面程序演示将打包 BCD 值 52933 按照小端格式(0x332905) 加载到内存中,并从它减去 BCD 值 28125 (0x258102) :
.section .data
value1:
    .byte 0x25, 0x81, 0x02
value2:
    .byte 0x33, 0x29, 0x05

.section .bss
    .lcomm result, 4

.section .text
.global _start
_start:
    nop
    xor %edi, %edi
    movl $3, %ecx
loop1:
    movb value2(, %edi, 1), %al
    sbbb value1(, %edi, 1), %al
    das
    movb %al, result(, %edi, 1)
    inc %edi
    loop loop1
    sbbb $0, result(, %edi, 4)
    movl $1, %eax
    movl $0, %ebx
    int $0x80
上面程序中,使用 SBB 指令使前一次减法操作留下的任何进位位都会被考虑在内,而不会漏减。减一次后就用 DAS 指令转换为打包的 BCD 格式,然后存储到 result 中。在 GDB 里可以看到,0x33 - 0x25 后得到 0x0e ,转换为打包二进制为 0x08;而 0x29 - 0x81 后得到 0xa8 (产生借位),转换后为打包二进制 0x48 。

DAS 指令是这样转换结果为打包 BCD 的:
因为在十六进制减法中,一次借的 1 位相当于十进制的 16 ,这比十进制一次借 10 要多出 6 ,所以转换时结果中的每一位要减去 6 。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-14 16:58 , Processed in 0.079458 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表