The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Nine (Part 2)

Table of Content

Chapter Nine (Part 4)

CHAPTER NINE:
ARITHMETIC AND LOGICAL OPERATIONS (Part 3)
9.3 - Multiprecision Operations
9.3.1 - Multiprecision Addition Operations
9.3.2 - Multiprecision Subtraction Operations
9.3.3 - Extended Precision Comparisons

9.3 Multiprecision Operations

One big advantage of assembly language over HLLs is that assembly language does not limit the size of integers. For example, the C programming language defines a maximum of three different integer sizes: short int, int, and long int. On the PC, these are often 16 or 32 bit integers. Although the 80x86 machine instructions limit you to processing eight, sixteen, or thirty-two bit integers with a single instruction, you can always use more than one instruction to process integers of any size you desire. If you want 256 bit integer values, no problem. The following sections describe how extended various arithmetic and logical operations from 16 or 32 bits to as many bits as you please.

9.3.1 Multiprecision Addition Operations

The 80x86 add instruction adds two 8, 16, or 32 bit numbers[1]. After the execution of the add instruction, the 80x86 carry flag is set if there is an overflow out of the H.O. bit of the sum. You can use this information to do multiprecision addition operations. Consider the way you manually perform a multidigit (multiprecision) addition operation:

Step 1: Add the least significant digits together:

         289                             289
        +456      produces              +456
        ----                            ----
                                           5 with carry 1.

Step 2: Add the next significant digits plus the carry:

          1 (previous carry)
         289                             289
        +456            produces        +456
        ----                            ----
           5                              45 with carry 1.

Step 3: Add the most significant digits plus the carry:

                                         1 (previous carry)
         289                             289
        +456            produces        +456
        ----                            ----
          45                             745

The 80x86 handles extended precision arithmetic in an identical fashion, except instead of adding the numbers a digit at a time, it adds them a byte or a word at a time. Consider the three-word (48 bit) addition operation shown below:

The add instruction adds the L.O. words together. The adc (add with carry) instruction adds all other word pairs together. The adc instruction adds two operands plus the carry flag together producing a word value and (possibly) a carry.

For example, suppose that you have two thirty-two bit values you wish to add together, defined as follows:

X               dword   ?
Y               dword   ?

Suppose, also, that you want to store the sum in a third variable, Z, that is likewise defined with the dword directive. The following 80x86 code will accomplish this task:

                mov     ax, word ptr X
                add     ax, word ptr Y
                mov     word ptr Z, ax
                mov     ax, word ptr X+2
                adc     ax, word ptr Y+2
                mov     word ptr Z+2, ax

Remember, these variables are declared with the dword directive. Therefore the assembler will not accept an instruction of the form mov ax, X because this instruction would attempt to load a 32 bit value into a 16 bit register. Therefore this code uses the word ptr coercion operator to coerce symbols X, Y, and Z to 16 bits. The first three instructions add the L.O. words of X and Y together and store the result at the L.O. word of Z. The last three instructions add the H.O. words of X and Y together, along with the carry out of the L.O. word, and store the result in the H.O. word of Z. Remember, address expressions of the form "X+2" access the H.O. word of a 32 bit entity. This is due to the fact that the 80x86 address space addresses bytes and it takes two consecutive bytes to form a word.

Of course, if you have an 80386 or later processor you needn't go through all this just to add two 32 bit values together, since the 80386 directly supports 32 bit operations. However, if you wanted to add two 64 bit integers together on the 80386, you would still need to use this technique.

You can extend this to any number of bits by using the adc instruction to add in the higher order words in the values. For example, to add together two 128 bit values, you could use code that looks something like the following:

BigVal1         dword   0,0,0,0         ;Four double words in 128 bits!
BigVal2         dword   0,0,0,0
BigVal3         dword   0,0,0,0
                 .
                 .
                 .
                mov     eax, BigVal1    ;No need for dword ptr operator since
                add     eax, BigVal2    ; these are dword variables.
                mov     BigVal3, eax

                mov     eax, BigVal1+4  ;Add in the values from the L.O.
                adc     eax, BigVal2+4  ; entity to the H.O. entity using
                mov     BigVal3+4, eax  ; the ADC instruction.

                mov     eax, BigVal1+8
                adc     eax, BigVal2+8
                mov     BigVal3+8, eax

                mov     eax, BigVal1+12
                adc     eax, BigVal2+12
                mov     BigVal3+12, eax

9.3.2 Multiprecision Subtraction Operations

Like addition, the 80x86 performs multi-byte subtraction the same way you would manually, except it subtracts whole bytes , words, or double words at a time rather than decimal digits. The mechanism is similar to that for the add operation, You use the sub instruction on the L.O. byte/word/double word and the sbb instruction on the high order values. The following example demonstrates a 32 bit subtraction using the 16 bit registers on the 8086:

var1            dword   ?
var2            dword   ?
diff            dword   ?

                mov     ax, word ptr var1
                sub     ax, word ptr var2
                mov     word ptr diff, ax
                mov     ax, word ptr var1+2
                sbb     ax, word ptr var2+2
                mov     word ptr diff+2, ax

The following example demonstrates a 128-bit subtraction using the 80386 32 bit register set:

BigVal1         dword   0,0,0,0         ;Four double words in 128 bits!
BigVal2         dword   0,0,0,0
BigVal3         dword   0,0,0,0
                .
                .
                .
                mov     eax, BigVal1    ;No need for dword ptr operator since
                sub     eax, BigVal2    ; these are dword variables.
                mov     BigVal3, eax

                mov     eax, BigVal1+4  ;Subtract the values from the L.O.
                sbb     eax, BigVal2+4  ; entity to the H.O. entity using
                mov     BigVal3+4, eax  ; the SUB and SBB instructions.

                mov     eax, BigVal1+8
                sbb     eax, BigVal2+8
                mov     BigVal3+8, eax

                mov     eax, BigVal1+12
                sbb     eax, BigVal2+12
                mov     BigVal3+12, eax

9.3.3 Extended Precision Comparisons

Unfortunately, there isn't a "compare with borrow" instruction that can be used to perform extended precision comparisons. Since the cmp and sub instructions perform the same operation, at least as far as the flags are concerned, you'd probably guess that you could use the sbb instruction to synthesize an extended precision comparison; however, you'd only be partly right. There is, however, a better way.

Consider the two unsigned values 2157h and 1293h. The L.O. bytes of these two values do not affect the outcome of the comparison. Simply comparing 21h with 12h tells us that the first value is greater than the second. In fact, the only time you ever need to look at both bytes of these values is if the H.O. bytes are equal. In all other cases comparing the H.O. bytes tells you everything you need to know about the values. Of course, this is true for any number of bytes, not just two. The following code compares two signed 64 bit integers on an 80386 or later processor:

; This sequence transfers control to location "IsGreater" if
; QwordValue > QwordValue2. It transfers control to "IsLess" if
; QwordValue < QwordValue2. It falls though to the instruction
; following this sequence if QwordValue = QwordValue2. To test for
; inequality, change the "IsGreater" and "IsLess" operands to "NotEqual"
; in this code.

                mov     eax, dword ptr QWordValue+4                             ;Get H.O. dword
                cmp     eax, dword ptr QWordValue2+4
                jg      IsGreater
                jl      IsLess
                mov     eax, dword ptr QWordValue
                cmp     eax, dword ptr QWordValue2
                jg      IsGreater
                jl      IsLess

To compare unsigned values, simply use the ja and jb instructions in place of jg and jl.

You can easily synthesize any possible comparison from the sequence above, the following examples show how to do this. These examples do signed comparisons, substitute ja, jae, jb, and jbe for jg, jge, jl, and jle (respectively) to do unsigned comparisons.

QW1             qword   ?
QW2             qword   ?

dp              textequ <dword ptr>

; 64 bit test to see if QW1 < QW2 (signed).
; Control transfers to "IsLess" label if QW1 < QW2. Control falls
; through to the next statement if this is not true.

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jg      NotLess
                jl      IsLess
                mov     eax, dp QW1             ;Fall through to here if H.O. 
                cmp     eax, dp QW2             ; dwords are equal.
                jl      IsLess
NotLess:

; 64 bit test to see if QW1 <= QW2 (signed).

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jg      NotLessEq
                jl      IsLessEq
                mov     eax, dp QW1
                cmp     eax, dword ptr QW2
                jle     IsLessEq
NotLessEQ:

; 64 bit test to see if QW1 >QW2 (signed).

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jg      IsGtr
                jl      NotGtr
                mov     eax, dp QW1             ;Fall through to here if H.O. 
                cmp     eax, dp QW2             ; dwords are equal.
                jg      IsGtr
NotGtr:

; 64 bit test to see if QW1 >= QW2 (signed).

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jg      IsGtrEq
                jl      NotGtrEq
                mov     eax, dp QW1
                cmp     eax, dword ptr QW2
                jge     IsGtrEq
NotGtrEq:

; 64 bit test to see if QW1 = QW2 (signed or unsigned). This code branches
; to the label "IsEqual" if QW1 = QW2. It falls through to the next instruction
; if they are not equal.

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jne     NotEqual
                mov     eax, dp QW1
                cmp     eax, dword ptr QW2
                je      IsEqual
NotEqual:

; 64 bit test to see if QW1 <> QW2 (signed or unsigned). This code branches
; to the label "NotEqual" if QW1 <> QW2. It falls through to the next 
; instruction if they are equal.

                mov     eax, dp QW1+4           ;Get H.O. dword
                cmp     eax, dp QW2+4
                jne     NotEqual
                mov     eax, dp QW1
                cmp     eax, dword ptr QW2
                jne     NotEqual

[1] As usual, 32 bit arithmetic is available only on the 80386 and later processors.

Chapter Nine (Part 2)

Table of Content

Chapter Nine (Part 4)

Chapter Nine: Arithmetic And Logical Operations (Part 3)
27 SEP 1996