BINARY   CODED   DECIMALS
by Raymond Filiatreault

Copyright 2005
Latest revision April 2005


INTRODUCTION

When computers were born, they didn't have highly sophisticated sytems. Although they were very slow when compared to modern computers, they could, however, crunch numbers much faster than humans. They soon became in demand by large corporations such as the banking industry where large numbers and absolute accuracy were expected. The algorithms simply simulated how computations were being done manually with the decimal system.

When the computers evolved to the desk top size with the advent of microchips, registers were initially the 8-bit type and computing with the decimal system still had to be provided. Some specific instructions were then made available to facilitate the task of the programmer for that aspect of computing. Those instructions have been maintained with every new development of microchips and will most probably continue to be provided in the future.

That set of instructions is referred to as the BCD (Binary Coded Decimal) instructions. There are only six of those instructions; four of those are for unpacked BCDs and the other two for packed BCDs.


Unpacked BCD format

This format is simply the allocation of one byte for each decimal digit. The value of each byte can only range from 0 to 9 (even though a byte can hold values from 0 to 255). A number is usually kept in memory in consecutive bytes in the order of decreasing values of the digits, i.e. in the same order they would appear as a string. This makes it easier to convert the number to its ASCII equivalent for display purposes.

However, keeping the digits in reverse order can also be used if preferred. The reverse order has the advantage that the number can be increased without the need to shift all the digits when a new most significant digit becomes necessary; it simply gets added in the next available byte.

For example, a variable could be initialized to 38519382765 in decreasing order of the relative value of the decimal digits as follows:

   myUnpackedBCD  db  3, 8, 5, 1, 9, 3, 8, 2, 7, 6, 5

and a hexadecimal memory dump of that data would show:

   03 08 05 01 09 03 08 02 07 06 05
The following code could be adapted to convert a null-terminated ASCII input string to its equivalent unpacked BCD data. Instructions are included to count the number of digits. Instructions could also be added to check for the validity of the numerical input.
.data
   inputbuf  db   "38519382765",0  ;size input buffer as required
   bcdbuf    db   32 dup(?)        ;size must be adapted for expected input
   digitcnt  dd   ?

.code
   mov   esi,offset inputbuf
   mov   edi,offset bcdbuf+?       ;start at end of buffer for reverse order
   xor   ecx,ecx       ;use for counting digits
@@:
   lodsb               ;get next ASCII character
   or    al,al         ;check for end of null-terminated string
   jz    @F
   and   al,0Fh        ;keep only the binary value of the numerical digit
   mov   [edi],al      ;store it
   inc/dec edi         ;depending on chosen order of digits
   inc   ecx           ;increment digit counter
   jmp   @B

@@:
   mov   digitcnt,ecx
A binary 0 is a valid BCD digit and could not be used to indicate the end of an unpacked BCD number. However, if necessary, a -1 (0FFh) could be inserted to signal the end of such a BCD number.

Packed BCD format

Storage memory being at a premium in the early computers, the use of a full byte for each digit was considered an enormous waste of storage space. The use of the upper 4 bits of the byte was therefore given consideration and a "compressed" format was born.

This format allocates one byte for each pair of decimal digits, starting from the least significant digit. The lower value digit of the pair is kept in the lower 4 bits and the higher value in the upper 4 bits of the byte. As for the unpacked BCDs, a number is usually kept in memory in consecutive bytes in the order of decreasing values of the digits. However, keeping the digits in reverse order can also be used as exemplified by the requirement of the FPU with the FBLD instruction.

For example, the same number as above would need to be declared as follows for a packed BCD in the reverse order:

   myPackedBCD  db  65h, 27h, 38h, 19h, 85h, 03h
and the hexadecimal memory dump would show:
   65 27 38 19 85 03
The following code could be adapted to convert a null-terminated ASCII input string to its equivalent packed BCD data. It assumes that the count of characters in the returned string is already available and that all characters have been verified to be numerical. Instructions are included to obtain the size of the packed number in bytes.
.data
   inputbuf  db   "38519382765",0  ;size input buffer as required
   bcdbuf    db   32 dup(?)        ;size must be adapted for expected input
   charcnt   dd   11               ;count of input string characters
   bytecnt   dd   ?                ;count of bytes in the packed BCD

.code
   mov   esi,offset inputbuf
   mov   edi,offset bcdbuf
   mov   ecx,charcnt
   add   esi,ecx           ;ESI now points to terminating 0
                           ;packing must start with least significant digit
   mov   bytecnt,0         ;initialize counter

@@:
   dec   esi
   mov   al,[esi]
   and   al,0Fh            ;keep only the binary value of the numerical digit
   dec   ecx
   jz    last_one          ;exit now if last character processed
   ror   ax,4              ;low nibble temporarily in upper 4 bits of AH
   dec   esi
   mov   al,[esi]
   and   al,0Fh            ;keep only the binary value of the numerical digit
   rol   ax,4              ;bring back low nibble to AL
   dec   ecx
   jz    last_one          ;exit if last character processed
   stosb                   ;store packed BCD byte
   inc   bytecnt           ;increment byte counter
   jmp   @B

last_one:
   stosb                   ;store last packed BCD byte
   inc   bytecnt           ;increment byte counter

; The packed BCD number is now stored in reverse order (least significant
; byte first). Instructions could be added to change that order if needed.
As for unpacked BCDs, a binary 0 is a valid packed BCD digit and could not be used to indicate the end of a packed BCD number. However, if necessary, a -1 (0FFh) could be inserted to signal the end of such a BCD number.

(Example code to convert a packed BCD back to a null-terminated ASCII string is included in the description of the daa and das instructions.)


Unpacked BCD instructions

The four instructions for unpacked BCDs are designed to act on the AL/AH registers following an addition, a subtraction and a multiplication, and prior to a division.

aaa (ASCII Adjust after Addition)

aas (ASCII Adjust after Subtraction)

aam (ASCII Adjust after Multiplication)

aad (ASCII Adjust before Division)


Packed BCD instructions

The two instructions for packed BCDs are designed to act on the AL register following an addition and a subtraction. (Note that packed BCDs cannot be used for multiplications and divisions.)

daa (Decimal Adjust after Addition)

das (Decimal Adjust after Subtraction)


If you wish, you can download the entire tutorial in HTML format. In addition to the tutorial itself, the .zip file also contains two additional folders.

One of them is the AAAfun containing a short program to add any keyboard characters and convert the resulting sum as if they were unpacked BCDs. The sum is then converted to ASCII characters by adding your own number. A few examples are given in a txt file. That folder also contains the source code and the resource file for the input dialog box.

The other folder contains my WINSQR program to extract square roots with up to 9999 decimal places based on the use of BCDs for the computation. A brief help file is incorporated into the program. Again, the folder also contains the source code and the resource file for the input dialog box. (That program was written some 5-6 years ago shortly after I started with Win32 asm; my style and knowledge of Win32 has evolved somewhat since then.)

BCDtut v1.0