0

i work on assembly for the X86 platform and i got an issue with doing bcd for numbers bigger than 99.

i got in 'AX' 123 (decimal) and i added to it 5 (decimal) then i did 'aaa' but instead of the result to be 128 its 0106. i saw the algorithm and understood why but how i do 'aaa' to number over 99
that's my code:

mov ax,123
add ax,5
aaa

i tried to divide 'AX' in 0ah to separate the number and use 'aad' but i saw that it's forbiden for numbers over 99.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    What result do you want? Obviously `128` can't be represented by 2 decimal digits, so there is not unpacked-BCD representation. Do you want the low 2 digits, `28`, as unpacked-BCD? Note that you're not even starting with valid unpacked-BCD inputs. That would be `mov ax, 0x0203` / `add ax, 5` for 23+5. – Peter Cordes Nov 28 '20 at 17:18
  • thank you for the answer, so is there a way to do it by dividing as i tried before? because that's a question we got to at home – timur45 Nov 28 '20 at 17:28
  • 1
    Sure, you can break a binary integer up into its decimal digits with repeated division. [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894) / [Displaying numbers with DOS](https://stackoverflow.com/q/45904075) – Peter Cordes Nov 28 '20 at 17:41
  • thank you but unfortunately i must work with bcd :\ – timur45 Nov 28 '20 at 18:08
  • Then you'll have to use extended-precision / multiple steps (like `adc` / `aaa` or `daa`) because a 3-digit number doesn't fit into 2 BCD digits. And you *definitely* can't start with `mov ax, 123` because that's a binary integer that fits in the low 8 bits, not even *packed* BCD in the low 3 nibbles of AX. – Peter Cordes Nov 28 '20 at 18:49
  • @PeterCordes: He probably needs to convert 123 to BCD. I don't remember how and I don't know why anybody's teaching BCD anymore. Don't these instructions fault in 64 bit mode? – Joshua Nov 29 '20 at 03:21
  • @Joshua: Correct, 64-bit mode freed up those legacy opcodes. I wouldn't say "convert", I'd say *start with* BCD, like `mov ax, 0x0123` for packed BCD. Then possibly `add al, 5` / `daa` / `adc ah, 0`, if we want to assume the high digit doesn't carry-out, and if I recall correctly that DAA will update CF according to decimal carry-out from AL. – Peter Cordes Nov 29 '20 at 03:28

1 Answers1

2

The 'AAA" instruction ("ASCII Adjust After Addition") only cares about the value in the lowest 4 bits of AL (it's literally "if(AL & 0x0F > 9) AX += (1 << 8) + (1 << 4) - 10;", or "if the previous addition caused the lowest nibble/digit to overflow; then subtract 10 from the lowest nibble/digit and add 1 to second nibble/digit to fix up the carry that should've happened but didn't, then add 1 plus the carry from second nibble/digit to the third nibble/digit while most people wish this didn't happen").

For 8-bit packed BCD addition (e.g. one digit in the lowest nibble of AL and another digit in the highest nibble of AL) this is a major pain in the neck - you have to store the first nibble somewhere then shift the value in AL to the right and do a second aaa to fix up the second nibble. This can be done with rotates - e.g. aaa then ror ax,4 then aaa then rol ax,4.

For 8-bit unpacked BCD addition (e.g. one digit in the lowest nibble of AL and another digit in the lowest nibble of AH) it's mostly the same major pain in the neck.

For "12-bit or larger" packed BCD addition it goes from bad to worse because first aaa causes the third nibble/digit to be potentially corrupted (increased by 1 too many if the first nibble overflowed), the second aaa potentially corrupts the fourth nibble/digit, etc.

For "12-bit or larger" unpacked BCD addition it goes from bad to worse because (for 16-bit code) now your digits have to be spread across multiple registers and you end up shuffling values into/out of AL just to use the aaa instruction.

The only sane solution is to never use aaa unless you're doing 4-bit addition (which is why it got removed in 64-bit code - nobody used it anyway).

The best alternative is to convert from BCD to normal integer at first sight, then do all the calculations using normal integers, then convert from normal integer back into BCD at the last possible opportunity. This approach can be especially nice if you can use the FPU - e.g. fbld to load an 18-digit (72 bit) packed BCD value into an FPU register while converting it into a normal floating point value and fbstp to convert a normal floating point value into BCD and store it.

Anyway...

    mov ax,123        ;This is not valid BCD - it's the value 0x7B or "7(11)" in decimal
    add ax,5          ;ax = 0x7B + 0x05 = 0x80
    aaa               ;This instruction does nothing because the lowest nibble is less than 9
                      ;Result = 0x80 or "80" in decimal

If the original value/s being added were valid BCD, then you might get something like:

    mov ax,121        ;ax = 0x79 or "79" in decimal
    add ax,5          ;ax = 0x79 + 0x05 = 0x7E
    aaa               ;0xE is is larger than 9, so "fix" it by adding 0x0106
                      ;ax = 0x0184
                      ;Result in AL = 0x84 or "84" in decimal

However, it this is a "lucky case" because the result fits in 2 digits. It could easily be more like:

    mov ax,152        ;ax = 0x98 or "98" in decimal
    add ax,5          ;ax = 0x99 + 0x05 = 0x9D
    aaa               ;0xD is is larger than 9, so "fix" it by adding 0x0106
                      ;ax = 0x01A3
                      ;Result in AX is a horribly broken mess (second digit not valid BCD, third digit correct)
Brendan
  • 35,656
  • 2
  • 39
  • 66