来自于《Intel汇编语言程序设计》(第五版)第七章的代码,使用AAA( ASCII adjust after addition )指令来调整ASCII相加之后的结果。源代码如下:
TITLE ASCII Addition (ASCII_add.asm)
; Perform ASCII arithmetic on strings having
; an implied fixed decimal point
INCLUDE Irvine32.inc
DECIMAL_OFFSET = 5 ; offset from right of string
.data
decimal_one BYTE "100123456789765" ; 1001234567.89765
decimal_two BYTE "900402076502015" ; 9004020765.02015
sum BYTE (SIZEOF decimal_one + 1) DUP(0),0
.code
main PROC
; start at the last digit position.
mov esi, SIZEOF decimal_one - 1
mov edi,SIZEOF decimal_one
mov ecx,SIZEOF decimal_one
mov bh,0 ; set carry value to zero
L1:
mov ah,0 ; clear AH before addition
mov al,decimal_one[esi] ; get the first digit
add al,bh ; add the previous carry
aaa ; adjust the sum ( AH = carry )
mov bh,ah ; save the carry in carry1
or bh,30h ; convert it to ASCII
add al,decimal_two[esi] ; add the second digit
aaa ; adjust the sum ( AH = carry )
or bh,ah ; OR the carry with carry1
or bh,30h ; convert it to ASCII
or al,30h ; convert AL back to ASCII
mov sum[edi],al ; save it in the sum
dec esi ; back up one digit
dec edi
loop L1
mov sum[edi],bh; ; save last carry digit
; Display the sum as a string.
mov edx,OFFSET sum
call WriteString
call crlf
exit
main ENDP
END main
下面开始逐句分析。
decimal_one BYTE "100123456789765" ; 1001234567.89765
decimal_one BYTE "900402076502015" ; 9004020765.02015
sum BYTE (SIZEOF decimal_one + 1) DUP(0),0
首先定义了两个数字字符串,用来隐含进行小数点的加法。随后定义了一个放置和的sum变量,这个变量多了一位来放置进位。
然后再来看代码段:
mov esi, SIZEOF decimal_one - 1
mov edi,SIZEOF decimal_one
mov ecx,SIZEOF decimal_one
mov bh,0 ; set carry value to zero
首先,为循环游标 esi 赋值为字符串的最后一个位置,即SIZEOF decimal_one - 1,此时即指向decimal_one的最后一位数字"5" 。 然后是sum的游标edi,为其赋值为SIZEOF decimal_one(因为我们这里比decimal_one的长度多了一位,用来存储进位),还有循环数ecx,赋值为decimal_one的长度。
最后把进位值bh设置为零。
然后让我们看一下接下来的代码:
L1:
mov ah,0 ; 在进行AAA指令之前,一定要清空ah,否则会影响AAA指令结果
mov al,decimal_one[esi] ; 得到decimal_one的esi位置的数字
add al,bh ; 将之前的进位值与al相加(例如,第一次相加时没有进位)
aaa ; 使用aaa调整相加结果,会将进位值保存到ah中
mov bh,ah ; 将进位值保存到bh中
or bh,30h ; 将此时的结果转化成ASCII码
add al,decimal_two[esi] ; 再与decimal_two的第esi位数字相加,保存到al中
aaa ; 调整加法结果,进位将会保存到AH中
or bh,ah ; 将现在的进位进行或操作(不太明白为什么)
or bh,30h ; 将进位值转化为ASCII码
or al,30h ; 将AL中的值转化为ASCII码
mov sum[edi],al ; 将计算完的此位值赋值到sum相应位
dec esi ; 向前移一位,取得下一个相加数的游标位置
dec edi ; 向前移一位,取得下一个相加数的游标位置
loop L1 ; 执行下次循环
mov sum[edi],bh; ; 计算完成之后,将最后一次的进位加到sum的第edi位置
; Display the sum as a string.
mov edx,OFFSET sum ; 将sum的偏移地址赋值到edx中
call WriteString ; 在屏幕上显示此时edx的值,也就是sum的结果
call crlf ; 光标移动到下一行开头
exit ; 退出(Irvine32中的函数)
到这里,程序分析便结束了,程序的结果将为:
1000525533291780
结果共为16位(原两个相加数为15位),并且隐含了小数点。
补充:
因为 ASCII 码的十进制数中的最高位总是0011b,所以才会进行 or bh,30h 的操作;其中未将ASCII码的十进制数的高四位设置为0011b的为未压缩格式,设置之后的才为ASCII格式,示例如下:
ASCII格式: 33 34 30 32
未压缩格式: 03 04 00 02