【问题标题】:How do ASCII Adjust and Decimal Adjust instructions work?ASCII 调整和小数调整指令如何工作?
【发布时间】:2014-06-07 00:55:05
【问题描述】:

我一直在努力理解 x86 汇编语言中的 ASCII 调整指令。

我在互联网上看到所有信息告诉我不同​​的事情,但我想这只是以不同的形式解释的同一件事,我仍然不明白。

谁能解释为什么在AAAAAS 的伪代码中,我们必须在 AL 的低位半字节中加减 6?

有人能解释一下AAMAAD和十进制调整指令伪代码in the Intel instruction set manuals,它们为什么会这样,它们背后的逻辑是什么?

最后,有人可以举例说明这些说明何时有用,或者至少在它们过去有用的应用中。

我知道现在这些指令已经不用了,但我还是想知道这些指令是如何工作的,很高兴知道。

【问题讨论】:

标签: assembly x86 decimal instructions bcd


【解决方案1】:

为什么在 AAA 的伪代码中,AAS 我们必须在 AL 的低位半字节中加减 6

因为在十六进制中每个字符有 16 个不同的值,而 BCD 只有 10 个。当你用十进制进行数学运算时,如果一个数字大于 10,你需要取 10 的模数并进位到下一行。同样,在 BCD 数学中,当加法的结果大于 9 时,您加 6 以跳过剩余的 6 个“无效”值并进位到下一位。相反,你在减法中减去 6。

例如:27 + 36

  27: 0010 0111
+ 36: 0011 0110
───────────────
5_13: 0101 1101 (13 >= 10)
+  6:      0110
───────────────
  63: 0110 0011 (13 + 6 = 19 = 0x13, where 0x3 is the units digit and 0x10 is the carry)

解压加法是一样的,只是你直接从个位进位到十位,丢弃每个字节的前半字节

更多信息可以阅读


有人能解释一下英特尔指令集手册中的 AAM、AAD 和小数调整指令伪代码吗,它们为什么会这样,它们背后的逻辑是什么?

AAM 只是从二进制到 BCD 的转换。您通常以二进制进行乘法运算,然后调用 AAM 将结果除以 10 并将商余数对存储在两个未压缩的 BCD 字符中

例如:

13*6 = 78 = 0100 1110
78/10 = 7 remains 8 => result = 0x78

AAD 是相反的:在除法之前,你调用 AAD 将其从 BCD 转换为二进制,并像其他二进制除法一样进行除法

例如:87/5

0x8*10 + 0x7 = 0x57
0x57/5 = 0x11 remains 0x7

这些指令的原因是因为过去,内存很昂贵,您必须尽可能减少内存使用量。因此在那个时代CISC CPU 非常普遍。他们使用大量复杂的指令来最小化用于执行任务的指令。现在内存便宜得多,现代架构几乎是 RISCy,需要权衡 CPU 复杂性和代码密度

【讨论】:

  • 完美解释!谢谢,我现在知道这是多么直观,我并没有真正阅读过 aad 和 aam 的作用,我认为它们会做更复杂的事情.....
  • 鉴于 AAD 和 AAM 接受立即字节参数并将累加器除以或乘以该值,我想知道英特尔为什么不以这样的方式指定它们?
  • @supercat 看了 AAD 和 AAM 的功能后我也想知道
  • 我发现一个更有趣的事情是 AAM 和 AAD 实际上可以处理任何字节立即数,这使它们成为真正的基本转换器。 AAD 类似于mul imm8,AAM 是类似的实现方式div imm8hugi.scene.org/online/coding/hugi%2017%20-%20coaax.htmrcollins.org/secrets/opcodes/AAD.htmlcode.google.com/p/corkami/wiki/x86oddities#aad
  • 英特尔的 ISA 手册 currently document AAM 的任意除数形式,无参数版本使用 0xa 只是汇编语法的东西。这在 2014 年没有得到充分记录,还是众所周知?无论如何,为了将来参考,这里有一些实际的数字 -> 2 位字符串的工作代码,一次使用 DIV,然后再次使用 AAM(不太方便,因为 AX 中的字节不是按打印顺序排列的)。 Displaying Time in Assembly
【解决方案2】:

我编写了一个程序来帮助理解 AAA After Addition。

.model small
.data
a db '1234'
len1 db $-a
b db '9876'
len2 db $-b
result db 05 dup(?)
len3 db $-result  

.code
main proc near
mov ax,@data
mov ds,ax
                    
lea bx,a
add bl,len1
mov si,bx

lea bx,b
add bl,len2
mov di,bx

dec si
dec di
dec len3
           
lea bx,result
add bl,len3
             
mov cl,len1  
mov ax,0h

l1:                            
mov al,[si]
mov dl,[di]
cmp ah,00h
je skip 
mov ah,0h
inc al                
skip:
    add al,dl
    aaa    
    or al,30h
    mov [bx],al
    dec bx 
    dec si
    dec di
    loop l1 
cmp ah,00h
je over
mov [bx],31h
jmp finish
over:
mov [bx],30h

finish:
        
mov ax,04ch
int 21h
endp 
end

现在正如您在“add”指令之后的程序中看到的,我们使用“aaa”将数字转换为 ASCII(30-39 对应于 0-9)。所以写出真正的输出,我们实际上需要将它恢复为十六进制,因为我们正在接受答案的“或”。现在使用“si”和“di”,我们将一个一个地加载数字并检查是否有进位,当我们执行“aaa”时我们会知道,因为当 digit 大于 9 时它将生成 digit ah,所以我们将“inc一个接一个。看看下面的“aaa”是如何工作的。

  AAA (ASCII Adjust after Addition)
  if low nibble of AL > 9 or AF = 1 then:
  AL = AL + 6  
  AH = AH + 1  
  AF = 1  
  CF = 1  
  else 
  AF = 0  
  CF = 0  
  in both cases: 
  clear the high nibble of AL. 

更多程序相关的 ASCII 加减乘除查看此链接。 GitHub

【讨论】:

  • 如果你想要一个指向数字字符串结尾的指针b,只需在此处放一个标签和mov bx, OFFSET b_end。或者至少将 len2 设为汇编时常量 (len2 equ $-b),以便您可以使用 mov bx, OFFSET b + len2。 (或者如果您想浪费一个字节的代码大小,则为 lea。)a 相同,因此您可以只执行 mov si, OFFSET a_end - 1 或其他操作,而不是所有这些运行时计算汇编时常量的东西。或者将指针-dec 指令放在循环之前内存访问。
  • 输出不是hexa十进制,它只是十进制。这就是为什么or al, '0' 可以工作,而不需要处理a..f 的情况,因为aaa 将总和分解为十进制 位。 (不是 ASCII,你是在 AAA 给你解压的 BCD 之后手动做的)
  • 另外,您的搬运操作看起来效率低下,而且不太容易理解。而不是比较 ah 和零,你可以做 add al, ah / mov ah, 0
猜你喜欢
  • 2014-05-27
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-10
相关资源
最近更新 更多