home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Unsorted BBS Collection
/
thegreatunsorted.tar
/
thegreatunsorted
/
programming
/
asm_programming
/
CHAP22-2.DOC
< prev
next >
Wrap
Text File
|
1990-07-31
|
38KB
|
827 lines
240
The first thing to talk about is the 8086 mnemonics. The four
instructions for unpacked BCD numbers are:
AAA ASCII adjust for addition
AAD ASCII adjust for division
AAM ASCII adjust for multiplication
AAS ASCII adjust for subtraction.
Even though all four instructions have ASCII as part of their
mnemonic, they have NOTHING to do with ASCII numbers. These
instructions operate on unpacked BCD numbers. They always give
results which are unpacked BCD numbers. Because of side effects
of the 8086 microcode, the add and subtract instructions do
something unusual, but this will be covered a little later.
Just like the packed BCD instructions, these four unpacked
instructions use the normal arithmetic operations and adjust the
results to compensate for their being unpacked BCD numbers. Let's
start with addition. The following program is like the BCD
program except that we use AAA (ascii adjust for addition)
instead of DAA (decimal adjust for addition).
; - - - - - - - - - - START CODE BELOW THIS LINE
mov ax_byte, 0C4h ; half registers, hex, hex
mov bx_byte, 94h ; half regs, signed, hex
mov dx_byte, 91h ; half regs, signed, signed
lea ax, ax_byte
call set_reg_style
mov cx, 0 ; clear cx for clarity
outer_loop:
mov ax, 0 ; clear the registers
mov bx, 0
mov dx, 0
call set_count
call show_regs
call get_hex_byte ; byte for al
mov dx, ax ; copy for dx
push ax ; save al
call get_hex_byte ; byte for bl
mov bl, al
mov bh, bl ; copy to bh
pop ax ; restore al
call show_regs_and_wait
add al, bl ; normal add
mov dx, ax ; copy to dx
call show_regs_and_wait
aaa ; make adjustment
mov dx, ax ; copy to dx
call show_regs_and_wait
jmp outer_loop
; + + + + + + + + + + + + + + + END CODE ABOVE THIS LINE
______________________
The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
Chapter 22 - BCD Numbers 241
________________________
Since this is a mixture of unpacked BCD instructions and normal
integer instructions, a copy of AX (which is showing hex) is
moved to DX (which is showing signed) each time AX is changed.
Also, a copy of BL is put in BH.
For the rest of this chapter, I will refer to the one's digit,
the ten's digit and the hundred's digit. In the number 346, 3 is
the hundred's digit, 4 is the ten's digit, and 6 is the one's
digit. In the number 928, 9 is the hundred's digit, 2 is the
ten's digit, and 8 is the one's digit.
If two valid unpacked BCD numbers are added, the result will be
between 0 and 18. This result MUST be in AL. AAA sets CF if this
result is greater than 9 ( i.e. there was a carry). AAA leaves
the one's digit in AL and adds the ten's digit to AH. (The ten's
digit can only be 0 for 0 - 9 or 1 for 10 - 18).
Run this. The standard input should be hex numbers between 00h
and 09h. When you feel comfortable with this, stop for a minute
and read on.
We now come to the peculiarity of this instruction. It isn't
operating on the whole byte, only the lower half byte.
Technically, if the LOWER HALF BYTE of each of the two numbers
which are added is a legitimate BCD digit, then at the end of AAA
the above results will be true and the UPPER HALF BYTE of AL will
be set to 0. If you put anything in the upper half byte, it will
be added by ADD, but will be blanked out (zeroed) by AAA.{1}
As a side effect of blanking out (zeroing) the upper half byte,
it is possible to use the ASCII numbers '0' (30h) to '9' (39h) as
addends. The result (after AAA) will still be a number between
00h and 09h with the carry flag either set or cleared, and AH
incremented if appropriate. Don't use it this way. The
multiplication and division instructions REQUIRE that the numbers
be between 00h and 09h, so all numbers should be changed into
unpacked BCD on data entry. Here is a partial list of things you
can add to get the result 07h:
03h + 04h 'c' + 'd' '+' + 't'
'3' + '4' 's' + 't' 'c' + '4'
'C' + 'D' '#' + '$' 'S' + '$'
As you can see, the addition instruction is pretty indiscriminate
about what kind of data you can put in and still get a legitimate
unpacked number out. It is your job to make sure that you have
____________________
1. They used the same microcode as for the beginning of DAA.
If the low half byte of AL is greater than 9, or if there was a
carry out of the low half byte (if AF, the auxillary flag was
set), then the 8086 adds 6 to AL, which shifts the excess out of
the low half byte. It then zeros the high half byte, sets the
carry flag, and increments AH. If you don't understand this, go
back and give the footnote on DAA a try.
The PC Assembler Tutor 242
______________________
legitimate unpacked data upon data entry.
If this is just a side effect of blanking the upper half byte,
why did they name the instruction "ascii adjust for addition"?
It's just that impish sense of humor of those Intel engineers. If
you think of these instructions as having anything to do with
ASCII numbers, you will only confuse yourself. These are
instructions on unpacked BCD numbers, period.
The other thing to notice is that this instruction increments the
AH register if there is a carry, so whenever you use AAA, it is
going to trash the AH register. Count on it. If AH contains
important data, store it somewhere else.
SUBTRACTION
When you have gotten used to how this works, look at subtraction.
The subtraction routine is the same as the addition routine
except that (1) ADD is replaced by SUB and (2) AAA is replaced by
AAS (ascii adjust for subtraction). If you generate a borrow, the
carry flag will be set and AH will be decremented by 1.
If you have two legitimate unpacked BCD numbers, then after
subtraction the result will be from +9 to -9. This result must be
in AL. After AAS (ascii adjust for subtraction), (1) if AL was
from 0 to +9, it will stay the same and CF will be cleared (CF=0)
or (2) if AL was -1 to -9, AAF will borrow 1 from AH (considering
it a ten's digit), and add 10 to AL. This will give a number from
+1 to +9. It will also set the carry flag (CF=1) to signal a
borrow. This is what you do with pencil and paper except that you
always do the borrow BEFORE the subtraction and AAS always does
the borrow AFTER the subtraction. Once again, this operates on
the LOW HALF BYTE, so what is in the upper half byte of the
numbers is irrelevant. It will be zeroed by AAS.
There is all sorts of data which will generate a legitimate
unpacked result; some of it is actually legitimate input. It too
trashes the AH register, so beware. Run this program till you see
what is going on with individual numbers.
MULTIPLICATION
There is also an instruction for multiplication. It assumes that
AL contains the result of the multiplication of two unpacked BCD
numbers. That is, 0 <= AL <= 81. After AAM (ascii adjust for
multiplication), AH will contain the 10's digit and AL will
contain the 1's digit. If AL is 75, then after AAM, AH will be 7
and AL will be 5. If AL is 48, then after AAM, AH will be 4 and
AL will be 8. (If AL is 183, an illegal result, then after AAM,
AH will be 18 and AL will be 3).
We are going to use a similar program for multiplication, but AX
and BL will be half byte unsigned; BH and DX will be half byte
hex. Every time we change AX, we will copy it to DX. Here is the
program:
Chapter 22 - BCD Numbers 243
________________________
; - - - - - - - - - - START CODE BELOW THIS LINE
mov ax_byte, 0A2h ; half registers, unsigned
mov bx_byte, 0C2h ; half regs, hex, unsigned
mov dx_byte, 0C4h ; half regs, hex
lea ax, ax_byte
call set_reg_style
mov cx, 0 ; clear cx for clarity
outer_loop:
mov ax, 0 ; clear the registers
mov bx, 0
mov dx, 0
call set_count
call show_regs
call get_hex_byte ; byte for al
mov dx, ax ; copy to dx
push ax ; save al
call get_hex_byte ; byte for bl
mov bl, al
mov bh, bl ; copy to bh
pop ax ; restore al
call show_regs_and_wait
mul bl ; unsigned multiplication
mov dx, ax ; copy to dx
call show_regs_and_wait
aam ; make adjustment
mov dx, ax ; copy to dx
call show_regs_and_wait
jmp outer_loop
; + + + + + + + + + + + + + + + END CODE ABOVE THIS LINE
All you need to change from the addition program is the register
style, ADD -> MUL and AAA -> AAM.
Try this out. Notice that after MUL, what we get in DX is
garbage. This number is a pure unsigned number, not a BCD number.
Just to underline how important it is to have legitimate unpacked
BCD data, try a few multiplications where the upper half byte is
non-zero and see what happens.
DIVISION
AAD (ascii adjust for division) is slightly different. What we
want to do is divide a number from 0 - 99 by a number from 1 to 9
(0 gives a zero divide interrupt). Therefore, AAD multiplies what
is in AH by 10 and adds it to what is in AL. It clears AH for the
coming unsigned division.{2} If AH contains 7 and AL contains 2,
then after AAD, AL will be 72 and AH will be 0. If AH is 4 and AL
is 6, then after AAD, AL will be 46 and AH will be 0. (If AH is
14 and AL is 7 - an illegal situation - then after AAD, AL will
____________________
2. Remember that for unsigned byte division, we set AH to 0.
This was back in the first chapter on division.
The PC Assembler Tutor 244
______________________
be 147 and AH will be 0)
After you have done AAD, you are ready for regular unsigned
division. After division, the quotient will be in AL and the
remainder will be in AH. Here's the program:
; - - - - - - - - - - START CODE BELOW THIS LINE
mov ax_byte, 0A2h ; half registers, unsigned
mov bx_byte, 0C2h ; half regs, hex, unsigned
mov dx_byte, 0C4h ; half regs, hex
lea ax, ax_byte
call set_reg_style
mov cx, 0 ; clear cx for clarity
outer_loop:
mov ax, 0 ; clear the registers
mov bx, 0
mov dx, 0
call set_count
call show_regs
call get_hex ; word
mov dx, ax ; copy to dx
push ax ; save al for later
call get_hex_byte ; byte for bl
mov bl, al
mov bh, bl ; copy to bh
pop ax ; get back al
call show_regs_and_wait
aad ; adjust for division
mov dx, ax ; copy to dx
call show_regs_and_wait
div bl ; unsigned division
mov dx, ax ; copy to dx
call show_regs_and_wait
jmp outer_loop
; + + + + + + + + + + + + + + + END CODE ABOVE THIS LINE
The differences from the multiplication routine are (1) we get a
TWO byte hex number for the dividend and (2) AAD comes first,
then DIV. That's all.
We need to enter a TWO byte unpacked BCD number in order to get
grist for the mill. 87 is 0807h, 93 is 0903h, 42 is 0402h. This
will give us two unpacked digits so we have something to put in
AH.
Run this program and enter legitimate (and if you want,
illegitimate) data. Notice that even with legitimate data, it is
possible to get a quotient that is larger than 9. (47 / 3 is 15,
remainder 2). Don't worry about this. We will avoid this problem
in the real program.
PACKING AND UNPACKING
Chapter 22 - BCD Numbers 245
________________________
Making an i/o routine is such a bother that we will enter data
with 'get_bcd' and output data with 'print_bcd'. To do this, we
need to pack and unpack the data. The calls in C would be:
unpack_bcd (&packed_number, &unpacked_number) ;
pack_bcd (&unpacked_number, &packed_number) ;{3}
The thing to be operated on is the first variable and the result
is the second variable. First, here's the unpacking routine:
; - - - - - - - - - -
unpack_bcd proc near
UNPACK_UNPACKED_ADDRESS EQU [bp + 6]
UNPACK_BCD_ADDRESS EQU [bp + 4]
push bp
mov bp, sp
PUSHREGS ax, bx, cx, si, di
mov si, UNPACK_BCD_ADDRESS
mov di, UNPACK_UNPACKED_ADDRESS
; start at top to unpack
add si, 9 ; bcd sign
add di, 18 ; unpacked sign
mov al, [si] ; move sign to unpacked
mov [di], al
dec si ; move down to next byte
dec di
mov cx, 9 ; 9 bcd data bytes
; unpack high byte first, then low byte
unpack_loop:
push cx ; save counter
mov al, [si] ; bcd byte to al
mov bl, al ; copy to bl
mov cl, 4
ror al, cl ; high half byte to low half
and al, 0Fh ; blank upper half byte
mov [di], al ; al is high half of bcd byte
dec di ; result pointer
and bl, 0Fh ; blank upper half byte
mov [di], bl ; bl is low half bcd byte
dec di ; result pointer
dec si ; source pointer
pop cx ; restore counter
loop unpack_loop
POPREGS ax, bx, cx, si, di
pop bp
ret
____________________
3. That '&' means that we are passing the addresses, not the
values.
The PC Assembler Tutor 246
______________________
unpack_bcd endp
; - - - - - - - - - - - - - - - -
This is pretty straightforward. First it moves the sign. Then,
taking a BCD byte, the routine divides it in two parts, rotating
the high half byte to the proper position, storing it,
DECREMENTING the pointer and storing the low half byte. The upper
half byte of each unpacked byte is zeroed. Notice that by
starting at the top, it is possible to unpack a number in place.
Diagram what is happening to make sure you believe that.
Here's the packing routine:
; - - - - - - - - - - - - - - - -
pack_bcd proc near
PACK_BCD_ADDRESS EQU [bp + 6]
PACK_UNPACKED_ADDRESS EQU [bp + 4]
push bp
mov bp, sp
PUSHREGS ax, bx, cx, si, di
mov si, PACK_UNPACKED_ADDRESS
mov di, PACK_BCD_ADDRESS
; start at bottom to pack
mov cx, 9 ; 9 bytes of bcd data
pack_loop:
push cx ; save counter
mov al, [si + 1] ; high unpacked byte
mov cl, 4
ror al, cl ; move to high half byte
or al, [si] ; OR low half with high half
mov [di], al ; move to BCD
inc di ; adjust pointers
add si, 2
pop cx ; restore counter
loop pack_loop
; si and di are now in the right place for the sign
mov al, [si]
mov [di], al
POPREGS ax, bx, cx, si, di
pop bp
ret
pack_bcd endp
; - - - - - - - -
This does the reverse process, rotating the high byte to the
proper place, then ORing the two together to form a packed BCD
byte. Notice that by starting at the BOTTOM it is possible to
pack a number in place. Diagram the action to make sure you
believe this.
Chapter 22 - BCD Numbers 247
________________________
These two routines allow us to use 19 byte unpacked BCD numbers.
Here's the multiplication routine for a 19 byte (18 data bytes
and 1 sign byte) unpacked number by a 1 byte unpacked number:
; - - - - -
unpacked_multiply proc near
PUSHREGS ax, bx, cx, dx, si, di
mov si, offset multiplicand
mov di, offset result
mov dh, 0 ; clear dh for carry
mov bl, multiplier_copy
mov cx, 18 ; 18 data bytes
mult_loop:
mov al, [si] ; multiplicand to al
mul bl ; multiply
add al, dh ; add old carry
aam ; adjust al
mov [di], al ; partial result
mov dh, ah ; save carry
inc si ; adjust pointers
inc di
loop mult_loop
mov extra_byte, ah ; extra byte
POPREGS ax, bx, cx, dx, si, di
ret
unpacked_multiply endp
; - - - - -
This is a clone of what we had in the chapter on multiple word
multiplication but is byte multiplication instead of word
multiplication. Refer back to that chapter to understand the
mechanics of the process. We store the partial result and save
the high byte for addition with the next multiplication. At the
end we save the 19th byte for printout.
We are cheating a little. we have:
mul bl ; multiply
add al, dh ; add old carry
aam ; adjust al
when we should have:
mul bl ; multiply
aam ; adjust al
add al, dh ; add old carry
aaa ; adjust al
The reason this works is that the maximum multiplication is
9X9=81. The maximum addition is 81+9 = 90. AAM will work
correctly with any number 99 or less, so we save a step; we do
one adjustment instead of two.
The PC Assembler Tutor 248
______________________
Now, let's look at the driver for the program:
; + + + + + START DATA BELOW THIS LINE
multiplier_message db "Enter a number from -9 to +9", 0
bcd_in dt ?
bcd_out dt ?
multiplicand db 19 dup (?)
multiplier db ?
multiplier_copy db ?
result db 19 dup (?)
extra_byte db ?
result_sign db ?
; + + + + + END DATA ABOVE THIS LINE
; + + + + + START CODE BELOW THIS LINE
outer_loop:
mov ax, offset bcd_in
call get_bcd
call print_bcd ; reprint for clarity
lea cx, multiplicand
push cx ; unpacked address
push ax ; bcd_in address
call unpack_bcd
add sp, 4 ; adjust the stack
mov al, multiplicand + 18 ; sign byte
mov result_sign, al ; either 00h or 80h
enter_multiplier:
lea ax, multiplier_message
call print_string
call get_signed_byte
; check for valid multiplier
cmp al, 9 ; > +9 ?
jg enter_multiplier
cmp al, -9 ; < -9?
jl enter_multiplier
; adjust multiplier sign
mov multiplier, al
mov multiplier_copy, al
and al, 80h ; sign bit set?
jz do_the_multiplication
; negative multiplier, so adjust
neg multiplier_copy ; make positive
xor result_sign, 80h ; reverse sign of result
do_the_multiplication:
call unpacked_multiply
mov al, result_sign ; transfer sign to result
mov result + 18, al
; pack the result
mov ax, offset bcd_out ; bcd number
push ax
Chapter 22 - BCD Numbers 249
________________________
mov ax, offset result ; unpacked number
push ax
call pack_bcd
add sp, 4 ; adjust the stack
; print multiplicand, multiplier, extra byte and result
mov ax, offset bcd_in
call print_bcd
mov al, multiplier
call print_signed_byte
mov al, extra_byte
call print_hex_byte
mov ax, offset bcd_out
call print_bcd
jmp outer_loop
; - - - - - END CODE ABOVE THIS LINE
We enter a multiplicand, unpack it, enter a number from -9 to +9
and save a copy of its absolute value as well as the sign the
result will be. We adjust the sign of the result after the
multiplication. No matter what you enter, the sign will be
correct, and if you put the 19th byte in front of the BCD number
which you have printed, the absolute value will be correct. We
are multiplying with a COPY of the multiplier, so we can reprint
the actual multiplier; it hasn't changed. At the end we pack the
result and print everything. The order of printout is
multiplicand, multiplier, extra byte and result.
Notice that we are not passing the parameters for
unpacked_multiply on the stack. This is pure whim. A rule for
when to pass on the stack is (1) If the subroutine doesn't know
where the parameters will be located in memory, you MUST pass
them on the stack but (2) if the subroutine knows for certain
where the parameters will be, it can fetch them itself.
DIVISION
Here is the division routine for 19 byte unpacked numbers:
; - - - - - - - -
unpacked_divide proc near
PUSHREGS ax, bx, cx, si, di
mov bl, divisor_copy
mov si, offset dividend + 17 ; start at the top
mov di, offset quotient + 17
mov cx, 18 ; 18 numeric bytes
mov ah, 0 ; clear ah for division
div_loop:
mov al, [si] ; dividend byte to al
aad ; adjust for unpacked number
div bl ; bl is divisor
mov [di], al ; move partial quotient
dec si ; decrement pointers
The PC Assembler Tutor 250
______________________
dec di
loop div_loop
mov remainder, ah ; final remainder
POPREGS ax, bx, cx, si, di
ret
unpacked_divide endp
; - - - - -
It is pretty simple; it too is a clone of the multiple word
division process. Go back to multiple word division if you don't
understand it. We keep the previous remainder for the next
division. Here is the driver:
; + + + + + + + + + + + + + + + START DATA BELOW THIS LINE
divisor_message db "Enter a number from -9 to +9 (but not 0).",0
bcd_in dt ?
bcd_out dt ?
quotient_sign db ?
remainder_sign db ?
divisor db ?
divisor_copy db ?
dividend db 19 dup (?)
quotient db 19 dup (?)
remainder db ?
; + + + + + + + + + + + + + + + END DATA ABOVE THIS LINE
; + + + + + + + + + + + + + + + START CODE BELOW THIS LINE
outer_loop:
mov ax, offset bcd_in
call get_bcd
call print_bcd ; reprint for clarity
lea cx, dividend
push cx ; unpacked address
push ax ; bcd_in address
call unpack_bcd
add sp, 4 ; adjust the stack
mov al, dividend + 18 ; sign byte
mov quotient_sign, al ; either 00h or 80h
mov remainder_sign, al ; ditto
enter_divisor:
lea ax, divisor_message
call print_string
call get_signed_byte
; check for valid divisor
cmp al, 0 ; 0?
je enter_divisor
cmp al, 9 ; > +9 ?
jg enter_divisor
cmp al, -9 ; < -9?
jl enter_divisor
; adjust divisor, quotient sign
mov divisor, al
Chapter 22 - BCD Numbers 251
________________________
mov divisor_copy, al
and al, 80h ; sign bit set?
jz do_the_division
; negative divisor, so adjust
neg divisor_copy ; make positive
xor quotient_sign, 80h ; reverse sign of quotient
do_the_division:
call unpacked_divide
mov al, quotient_sign ; transfer sign to quotient
mov quotient + 18, al
test remainder_sign, 0FFh ; positive or negative?
jz pack_the_quotient
neg remainder ; make the remainder negative
pack_the_quotient:
mov ax, offset bcd_out ; bcd number
push ax
mov ax, offset quotient ; unpacked number
push ax
call pack_bcd
add sp, 4 ; adjust the stack
; print dividend, divisor, quotient and remainder
mov ax, offset bcd_in
call print_bcd
mov al, divisor
call print_signed_byte
mov ax, offset bcd_out
call print_bcd
mov al, remainder
call print_signed_byte
jmp outer_loop
; + + + + + + + + + + + + + + + END CODE ABOVE THIS LINE
Enter a BCD number, unpack it. Enter a signed number, do range
checking, and if it is ok, store it (along with a copy, which we
make sure is positive). Calculate the sign of the quotient and
remainder. Do the division, then print the dividend, divisor,
quotient and remainder. (which have been sign adjusted). The
remainder is a signed byte.
Like unpacked_multiply, unpacked_divide fetches the parameters
directly from memory rather than from the stack.
The PC Assembler Tutor 252
______________________
SUMMARY
PACKED BCD INSTRUCTIONS
DAA (decimal adjust for addition) adjusts AL, assuming that it
contains the result of a legitimate packed BCD addition. It
treats AL as two independent half-bytes. If the result of the
lower half-byte is 10 or over, it subtracts 10 from the lower
half-byte and adds the carry to the upper half byte. It then
looks at the upper half byte. If its result is 10 or over, DAA
subtracts 10 from the upper half byte and sets the carry flag.
Otherwise the carry flag is cleared.
DAS (decimal adjust for subtraction) adjusts AL, assuming that it
contains the result of a legitimate packed BCD subtraction.It
treats AL as two independent half-bytes. If the result of the
lower half-byte is -1 or less, it adds 10 to the lower half-byte
and borrows 1 from the upper half byte. It then looks at the
upper half byte. If its result is -1 or less, DAS adds 10 to the
upper half byte and sets the carry flag to indicate a borrow.
Otherwise the carry flag is cleared.
UNPACKED BCD INSTRUCTIONS
AAA (ascii adjust for addition) adjusts AL, assuming that it
contains the result of a legitimate unpacked BCD addition. If the
lower half-byte has generated a result 10 or over, it subtracts
10, carries 1 to AH, and sets the carry flag. If the result is 9
or less, it clears CF. In either case it zeroes the upper
half-byte of AL.
AAD (ascii adjust for division) PREPARES AL and AH for division.
It assumes that AH contains the 10's digit and AL contains the
1's digit of a two byte unpacked BCD number. It multiplies AH by
10 and adds it to AL, thus making a single integer between 0 and
99. It zeroes AH in preparation for division.
AAM (ascii adjust for multiplication) adjusts AL, assuming that
it contains the result of a legitimate BCD multiplication. It
divides the result by 10, putting the quotient in AH and the
remainder in AL.
AAS (ascii adjust for subtraction) adjusts AL, assuming that it
contains the result of a legitimate unpacked BCD subtraction. If
the lower half-byte has generated a result -1 or less, it borrows
1 from AH, adds 10 to AL, and sets the carry flag. If the result
is 0 or more, it clears CF. In either case it zeroes the upper
half-byte of AL