How do ASCII Adjust and Decimal Adjust instructions work?
Asked Answered
S

2

8

I've been struggling with understanding the ASCII adjust instructions from x86 assembly language.

I see all over the internet information telling me different things, but I guess it's just the same thing explained in a different form that I still don't get.

Can anyone explain why in the pseudo-code of AAA, AAS we have to add, subtract 6 from the low-order nibble in AL?

And can someone explain AAM, AAD and the Decimal adjust instructions pseudo-code in the Intel instruction set manuals too, why are they like that, what's the logic behind them?

And at last, can someone give examples when these instructions can be useful, or at least in what applications they have been useful in the past.

I know that nowadays these instructions aren't used, but I still want to know how these instructions work, it's good to know.

Sarabia answered 7/6, 2014 at 0:55 Comment(1)
en.wikipedia.org/wiki/Intel_BCD_opcodeNoma
N
12

why in the pseudo-code of AAA, AAS we have to add, subtract 6 from the low-order nibble in AL

Because in hexadecimal each character has 16 distinct values and BCD has only 10. When you do math in decimal, if a number is larger than 10 you need to take the modulus of 10 and carry to the next row. Similarly, in BCD math, when the result of the addition is larger than 9 you add 6 to skip the 6 remaining "invalid" values and carry to the next digit. Conversely you subtract 6 in subtractions.

For example: 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)

Doing unpacked addition is the same except that you carry directly from the units digit to the tens digit, discarding the top nibbles of each byte

For more information you can read


and can someone explain AAM, AAD and the Decimal adjust instructions pseudo-code in the Intel instruction set manuals too, why are they like that, what's the logic behind them?

AAM is just a conversion from binary to BCD. You do the multiplication normally in binary, then calling AAM divides the result by 10 and store the quotient-remainder pair in two unpacked BCD characters

For example:

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

AAD is the reverse: before the division, you call AAD to convert it from BCD to binary and do the division just like other binary divisions

For example: 87/5

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

The reason for those instruction is because in the past, memories are expensive and you must reduce the memory usage as much as possible. Hence in that era CISC CPUs are very common. They use lots of complex instructions to minimize the instructions used to do a task. Nowadays memory is much cheaper and modern architectures are almost RISCy, with the trade off of CPU complexity and code density

Noma answered 7/6, 2014 at 2:28 Comment(5)
Perfect explanation! Thank you, I now see how intuitive that is, and how I didn't really read what aad and aam do, I thought they do even more complicated stuff.....Sarabia
Given that AAD and AAM accept an immediate byte argument and either divide or multiply the accumulator by that value, I wonder why Intel didn't specify them in such terms?Khaki
@Khaki after reading the function of AAD and AAM I wondered that tooNoma
an even more interesting thing that I've found is that AAM and AAD actually work with any byte immediates, which make them a real base converter. AAD is like mul imm8 and AAM is a similar way to achieve div imm8 hugi.scene.org/online/coding/hugi%2017%20-%20coaax.htm rcollins.org/secrets/opcodes/AAD.html code.google.com/p/corkami/wiki/x86oddities#aadNoma
Intel's ISA manuals currently document the arrbitrary-divisor form of AAM, and it's only an assembler syntax thing that the no-args version uses 0xa. Was this not well-documented in 2014, or well known? Anyway, for future reference, here's some actual working code for number -> 2-digit string, once using DIV and again using AAM (less convenient because bytes in AX aren't in printing order). Displaying Time in AssemblyDeflocculate
E
0

I writing one program which will help to understand 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

Now As you can see in program after "add" instruction we are using "aaa" which convert number into ASCII (30-39 correspond to 0-9). So writing real output we actually need to it back hexadecimal for that we are taking "or" of the answer. Now with "si" and "di" we are loading digits one by one and checking if there is carry as when we do "aaa" we will know because when digit is greater than 9 it will generate digit ah and so we will "inc" al by one.See how "aaa" works below.

  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. 

For More Programs related ASCII addition,subtraction,multiplication and division Check this link. GitHub

Episcopate answered 8/10, 2020 at 17:5 Comment(3)
If you want a pointer to the end of the digit-string b, just put a label there and mov bx, OFFSET b_end. Or at least make len2 an assemble-time constant (len2 equ $-b) so you can do mov bx, OFFSET b + len2. (Or lea if you want to waste a byte of code-size.) Same for a so you can just do mov si, OFFSET a_end - 1 or something, instead of all this runtime calculation of assemble-time constant stuff. Or put the pointer-dec instructions in the loop before the memory accesses.Deflocculate
The output isn't hexadecimal, it's just decimal. That's why or al, '0' works, not needing to handle the a..f case, because aaa breaks up the sum into decimal digits. (Not ASCII, you're doing that manually after AAA gives you unpacked BCD)Deflocculate
Also, your carry handling looks inefficient and isn't very easy to follow. Instead of comparing ah against zero, you could just do add al, ah / mov ah, 0Deflocculate

© 2022 - 2024 — McMap. All rights reserved.