Chapter 4
Defining and Using Simple 
This chapter covers the concepts essential for working with simple data types in assemblylanguage programs. The first section shows how to declare integer variables. The second section describes basic operations including moving, loading, and signextending numbers, as well as calculating. The last section describes how to do various operations with numbers at the bit level, such as using bitwise logical instructions and shifting and rotating bits.
The complex data types introduced in the next chapter — arrays, strings, structures, unions, and records — use many of the operations illustrated in this chapter. Floatingpoint operations require a different set of instructions and techniques. These are covered in Chapter 6, “Using FloatingPoint and Binary Coded Decimal
Numbers.”
An integer is a whole number, such as 4 or 4,444. Integers have no fractional part, as do the real numbers discussed in Chapter 6. You can initialize integer variables in several ways with the data allocation directives. This section explains how to use the SIZEOF and TYPE operators to provide information to the assembler about the types in your program. For information on symbolic integer constants, see “Integer Constants and Constant Expressions” in Chapter 1.
When you declare an integer variable by assigning a label to a data allocation directive, the assembler allocates memory space for the integer. The variable’s name becomes a label for the memory space. The syntax is:
[[name]] directive initializer

The following directives indicate the integer’s size and value range:
See Chapter 6 for information on the REAL4, REAL8, and REAL10 directives that allocate real numbers.
The SIZEOF and TYPE operators, when applied to a type, return the size of an integer of that type. The size attribute associated with each data type is:
Data Type 
Bytes 


BYTE, SBYTE 
1 
WORD, SWORD 
2 
DWORD, SDWORD 
4 
FWORD 
6 
QWORD 
8 
TBYTE 
10 
The data types SBYTE, SWORD, and SDWORD tell the assembler to treat the initializers as signed data. It is important to use these signed types with highlevel constructs such as .IF, .WHILE, and .REPEAT, and with PROTO and INVOKE directives. For descriptions of these directives, see the sections “LoopGenerating Directives,” “Declaring Procedure Prototypes,” and “Calling Procedures with INVOKE” in Chapter 7.
The assembler stores integers with the least significant bytes lowest in memory. Note that assembler listings and most debuggers show the bytes of a word in the opposite order — high byte first.
Figure 4.1 illustrates the integer formats.
Figure 4 . 1 Integer Formats
Although the TYPEDEF directive’s primary purpose is to define pointer variables (see “Defining Pointer Types with TYPEDEF” in Chapter 3), you can also use TYPEDEF to create an alias for any integer type. For example, these declarations
char TYPEDEF SBYTE
long TYPEDEF DWORD
float TYPEDEF REAL4
double TYPEDEF REAL8
allow you to use char, long, float, or double in your programs if you prefer the C data labels.
You can initialize variables when you declare them with constants or expressions that evaluate to constants. The assembler generates an error if you specify an initial value too large for the variable type.
A ? in place of an initializer indicates you do not require the assembler to initialize the variable. The assembler allocates the space but does not write in it. Use ? for buffer areas or variables your program will initialize at run time.
You can declare and initialize variables in one step with the data directives, as these examples show.
integer BYTE 16 ; Initialize byte to 16
negint SBYTE 16 ; Initialize signed byte to 16
expression WORD 4*3 ; Initialize word to 12
signedexp SWORD 4*3 ; Initialize signed word to 12
empty QWORD ? ; Allocate uninitialized long int
BYTE 1,2,3,4,5,6 ; Initialize six unnamed bytes
long DWORD 4294967295 ; Initialize doubleword to
; 4,294,967,295
longnum SDWORD 2147433648 ; Initialize signed doubleword
; to 2,147,433,648
tb TBYTE 2345t ; Initialize 10byte binary number
For information on arrays and on using the DUP operator to allocate initializer lists, see “Arrays and Strings” in Chapter 5.
Once you have declared integer variables in your program, you can use them to copy, move, and signextend integer variables in your MASM code. This section shows how to do these operations as well as how to add, subtract, multiply, and divide numbers and do bitlevel manipulations with logical, shift, and rotate instructions.
Since MASM instructions require operands to be the same size, you may need to operate on data in a size other than that originally declared. You can do this with the PTR operator. For example, you can use the PTR operator to access the highorder word of a DWORDsize variable. The syntax for the PTR operator is
type PTR expression
where the PTR operator forces expression to be treated as having the type specified. An example of this use is
.DATA
num DWORD 0
.CODE
mov ax, WORD PTR num[0] ; Loads a wordsize value from
mov dx, WORD PTR num[2] ; a doubleword variable
The primary instructions for moving data from operand to operand and loading them into registers are MOV (Move), XCHG (Exchange), CWD (Convert Word to Double), and CBW (Convert Byte to Word).
The most common method of moving data, the MOV instruction, is essentially a copy instruction, since it always copies the source operand to the destination operand without affecting the source. After a MOV instruction, the source and destination operands contain the same value.
The following example illustrates the MOV instruction. As explained in “GeneralPurpose Registers,” Chapter 1, you cannot move a value from one location in memory to another in a single operation.
; Immediate value moves
mov ax, 7 ; Immediate to register
mov mem, 7 ; Immediate to memory direct
mov mem[bx], 7 ; Immediate to memory indirect
; Register moves
mov mem, ax ; Register to memory direct
mov mem[bx], ax ; Register to memory indirect
mov ax, bx ; Register to register
mov ds, ax ; General register to segment register
; Direct memory moves
mov ax, mem ; Memory direct to register
mov ds, mem ; Memory to segment register
; Indirect memory moves
mov ax, mem[bx] ; Memory indirect to register
mov ds, mem[bx] ; Memory indirect to segment register
; Segment register moves
mov mem, ds ; Segment register to memory
mov mem[bx], ds ; Segment register to memory indirect
mov ax, ds ; Segment register to general register

The following example shows several common types of moves that require two instructions.
; Move immediate to segment register
mov ax, DGROUP ; Load AX with immediate value
mov ds, ax ; Copy AX to segment register
; Move memory to memory
mov ax, mem1 ; Load AX with memory value
mov mem2, ax ; Copy AX to other memory
; Move segment register to segment register
mov ax, ds ; Load AX with segment register
mov es, ax ; Copy AX to segment register
The MOVSX and MOVZX instructions for the 80386/486 processors extend and copy values in one step. See “Extending Signed and Unsigned Integers,” following.
The XCHG (Exchange) instruction exchanges the data in the source and destination operands. You can exchange data between registers or between registers and memory, but not from memory to memory:
xchg ax, bx ; Put AX in BX and BX in AX
xchg memory, ax ; Put "memory" in AX and AX in "memory"
; xchg mem1, mem2 ; Illegal can't exchange memory locations
Since moving data between registers of different sizes is illegal, you must “signextend” integers to convert signed data to a larger size. Signextending means copying the sign bit of the unextended operand to all bits of the operand’s next larger size. This widens the operand while maintaining its sign and value.
8086based processors provide four instructions specifically for signextending. The four instructions act only on the accumulator register (AL, AX, or EAX), as shown in the following list.
Instruction 
Signextend 


CBW (convert byte to word) 
AL to AX 
CWD (convert word to doubleword) 
AX to DX:AX 
CWDE (convert word to doubleword extended)* 
AX to EAX 
CDQ (convert doubleword to quadword)* 
EAX to EDX:EAX 
*Requires an extended register and applies only to 80386/486 processors.
On the 80386/486 processors, the CWDE instruction converts a signed 16bit value in AX to a signed 32bit value in EAX. The CDQ instruction converts a signed 32bit value in EAX to a signed 64bit value in the EDX:EAX register pair.
This example converts signed integers using CBW, CWD, CWDE, and CDQ.
.DATA
mem8 SBYTE 5
mem16 SWORD +5
mem32 SDWORD 5
.CODE
.
.
.
mov al, mem8 ; Load 8bit 5 (FBh)
cbw ; Convert to 16bit 5 (FFFBh) in AX
mov ax, mem16 ; Load 16bit +5
cwd ; Convert to 32bit +5 (0000:0005h) in DX:AX
mov ax, mem16 ; Load 16bit +5
cwde ; Convert to 32bit +5 (00000005h) in EAX
mov eax, mem32 ; Load 32bit 5 (FFFFFFFBh)
cdq ; Convert to 64bit 5
; (FFFFFFFF:FFFFFFFBh) in EDX:EAX
These four instructions efficiently convert unsigned values as well, provided the sign bit is zero. This example, for instance, correctly widens mem16 whether you treat the variable as signed or unsigned.
The processor does not differentiate between signed and unsigned values. For instance, the value of mem8 in the previous example is literally 251 (0FBh) to the processor. It ignores the human convention of treating the highest bit as an indicator of sign. The processor can ignore the distinction between signed and unsigned numbers because binary arithmetic works the same in either case.
If you add 7 to mem8, for example, the result is 258 (102h), a value too large to fit into a single byte. The bytesized mem8 can accommodate only the leastsignificant digits of the result (02h), and so receives the value of 2. The result is the same whether we treat mem8 as a signed value (5) or unsigned value (251).
This overview illustrates how the programmer, not the processor, must keep track of which values are signed or unsigned, and treat them accordingly. If AL=127 (01111111y), the instruction CBW sets AX=127 because the sign bit is zero. If AL=128 (10000000y), however, the sign bit is 1. CBW thus sets AX=65,280

(FF00h), which may not be what you had in mind if you assumed AL originally held an unsigned value.To widen unsigned values, explicitly set the higher register to zero, as shown in the following example:
.DATA
mem8 BYTE 251
mem16 WORD 251
.CODE
.
.
.
mov al, mem8 ; Load 251 (FBh) from 8bit memory
sub ah, ah ; Zero upper half (AH)
mov ax, mem16 ; Load 251 (FBh) from 16bit memory
sub dx, dx ; Zero upper half (DX)
sub eax, eax ; Zero entire extended register (EAX)
mov ax, mem16 ; Load 251 (FBh) from 16bit memory
The 80386/486 processors provide instructions that move and extend a value to a larger data size in a single step. MOVSX moves a signed value into a register and signextends it. MOVZX moves an unsigned value into a register and zero
extends it.
; 80386/486 instructions
movzx dx, bl ; Load unsigned 8bit value into
; 16bit register and zeroextend
These special 80386/486 instructions usually execute much faster than the equivalent 8086/286 instructions.
You can use the ADD, ADC, INC, SUB, SBB, and DEC instructions for adding, incrementing, subtracting, and decrementing values in single registers. You can also combine them to handle larger values that require two registers for storage.
The ADD, INC (Increment), SUB, and DEC (Decrement) instructions operate on 8 and 16bit values on the 8086–80286 processors, and on 8, 16, and 32bit values on the 80386/486 processors. They can be combined with the ADC and SBB instructions to work on 32bit values on the 8086 and 64bit values on the 80386/486 processors. (See “Adding and Subtracting in Multiple Registers,” following.)

These instructions have two requirements:
1. If there are two operands, only one operand can be a memory operand.
2. If there are two operands, both must be the same size.
To meet the second requirement, you can use the PTR operator to force an operand to the size required. (See “Working with Simple Variables,” previous.) For example, if Buffer is an array of bytes and BX points to an element of the array, you can add a word from Buffer with
add ax, WORD PTR Buffer[bx] ; Add word from byte array
The next example shows 8bit signed and unsigned addition and subtraction.
.DATA
mem8 BYTE 39
.CODE
; Addition
; signed unsigned
mov al, 26 ; Start with register 26 26
inc al ; Increment 1 1
add al, 76 ; Add immediate 76 + 76
;  
; 103 103
add al, mem8 ; Add memory 39 + 39
;  
mov ah, al ; Copy to AH 114 142
+overflow
add al, ah ; Add register 142
; 
; 28+carry
; Subtraction
; signed unsigned
mov al, 95 ; Load register 95 95
dec al ; Decrement 1 1
sub al, 23 ; Subtract immediate 23 23
;  
; 71 71
sub al, mem8 ; Subtract memory 122 122
;  
; 51 205+sign
mov ah, 119 ; Load register 119
sub al, ah ; and subtract 51
; 
; 86+overflow
The INC and DEC instructions treat integers as unsigned values and do not update the carry flag for signed carries and borrows.
When the sum of 8bit signed operands exceeds 127, the processor sets the overflow flag. (The overflow flag is also set if both operands are negative and the sum is less than or equal to 128.) Placing a JO (Jump on Overflow) or INTO (Interrupt on Overflow) instruction in your program at this point can transfer control to errorrecovery statements. When the sum exceeds 255, the processor sets the carry flag. A JC (Jump on Carry) instruction at this point can transfer control to errorrecovery statements.
In the previous subtraction example, the processor sets the sign flag if the result goes below 0. At this point, you can use a JS (Jump on Sign) instruction to transfer control to errorrecovery statements. Jump instructions are described in the “Jumps” section in Chapter 7.
You can add and subtract numbers larger than the register size on your processor with the ADC (Add with Carry) and SBB (Subtract with Borrow) instructions. If the operations prior to an ADC or SBB instruction do not set the carry flag, these instructions are identical to ADD and SUB. When you operate on large values in more than one register, use ADD and SUB for the least significant part of the number and ADC or SBB for the most significant part.
The following example illustrates multipleregister addition and subtraction. You can also use this technique with 64bit operands on the 80386/486 processors.
.DATA
mem32 DWORD 316423
mem32a DWORD 316423
mem32b DWORD 156739
.CODE
.
.
.
; Addition
mov ax, 43981 ; Load immediate 43981
sub dx, dx ; into DX:AX
add ax, WORD PTR mem32[0] ; Add to both + 316423
adc dx, WORD PTR mem32[2] ; memory words 
; Result in DX:AX 360404
; Subtraction
mov ax, WORD PTR mem32a[0] ; Load mem32 316423
mov dx, WORD PTR mem32a[2] ; into DX:AX
sub ax, WORD PTR mem32b[0] ; Subtract low  156739
sbb dx, WORD PTR mem32b[2] ; then high 
; Result in DX:AX 159684

For 32bit registers on the 80386/486 processors, only two steps are necessary. If your program needs to be assembled for more than one processor, you can assemble the statements conditionally, as shown in this example:
.DATA
mem32 DWORD 316423
mem32a DWORD 316423
mem32b DWORD 156739
p386 TEXTEQU (@Cpu AND 08h)
.CODE
.
.
.
; Addition
IF p386
mov eax, 43981 ; Load immediate
add eax, mem32 ; Result in EAX
ELSE
.
. ; do steps in previous example
.
ENDIF
; Subtraction
IF p386
mov eax, mem32a ; Load memory
sub eax, mem32b ; Result in EAX
ELSE
.
. ; do steps in previous example
.
ENDIF
Since the status of the carry flag affects the results of calculations with ADC and SBB, be sure to turn off the carry flag with the CLC (Clear Carry Flag) instruction or use ADD or SUB for the first calculation, when appropriate.
The 8086 family of processors uses different multiplication and division instructions for signed and unsigned integers. Multiplication and division instructions also have special requirements depending on the size of the operands and the processor the code runs on.
The MUL instruction multiplies unsigned numbers. IMUL multiplies signed numbers. For both instructions, one factor must be in the accumulator register (AL for 8bit numbers, AX for 16bit numbers, EAX for 32bit numbers). The other factor can be in any single register or memory operand. The result overwrites the contents of the accumulator register.
Multiplying two 8bit numbers produces a 16bit result returned in AX. Multiplying two 16bit operands yields a 32bit result in DX:AX. The 80386/486 processor handles 64bit products in the same way in the EDX:EAX pair.
This example illustrates multiplication of signed 16 and 32bit integers.
.DATA
mem16 SWORD 30000
.CODE
.
.
.
; 8bit unsigned multiply
mov al, 23 ; Load AL 23
mov bl, 24 ; Load BL * 24
mul bl ; Multiply BL 
; Product in AX 552
; overflow and carry set
; 16bit signed multiply
mov ax, 50 ; Load AX 50
; 30000
imul mem16 ; Multiply memory 
; Product in DX:AX 1500000
; overflow and carry set
A nonzero number in the upper half of the result (AH for byte, DX or EDX for word) sets the overflow and carry flags.
On the 80186–80486 processors, the IMUL instruction supports three additional operand combinations. The first syntax option allows for 16bit multipliers producing a 16bit product or 32bit multipliers for 32bit products on the 80386/486. The result overwrites the destination. The syntax for this operation is:
IMUL register16, immediate
The second syntax option specifies three operands for IMUL. The first operand must be a 16bit register operand, the second a 16bit memory (or register) operand, and the third a 16bit immediate operand. IMUL multiplies the memory (or register) and immediate operands and stores the product in the register operand with this syntax:
IMUL register16,{ memory16  register16}, immediate

For the 80386/486 only, a third option for IMUL allows an additional operand for multiplication of a register value by a register or memory value. The syntax is:
IMUL register,{register  memory}
The destination can be any 16bit or 32bit register. The source must be the same size as the destination.
In all of these options, products too large to fit in 16 or 32 bits set the overflow and carry flags. The following examples show these three options for IMUL.
imul dx, 456 ; Multiply DX times 456 on 8018680486
imul ax, [bx],6 ; Multiply the value pointed to by BX
; by 6 and put the result in AX
imul dx, ax ; Multiply DX times AX on 80386
imul ax, [bx] ; Multiply AX by the value pointed to
; by BX on 80386
The IMUL instruction with multiple operands can be used for either signed or unsigned multiplication, since the 16bit product is the same in either case. To get a 32bit result, you must use the singleoperand version of MUL or IMUL.
The DIV instruction divides unsigned numbers, and IDIV divides signed numbers. Both return a quotient and a remainder.
Table 4.1 summarizes the division operations. The dividend is the number to be divided, and the divisor is the number to divide by. The quotient is the result. The divisor can be in any register or memory location except the registers where the quotient and remainder are returned.
Table 4.1 Division Operations


Size of 
Dividend Register 
Size of Divisor 




16 bits 
AX 
8 bits 
AL 
AH 
32 bits 
DX:AX 
16 bits 
AX 
DX 
64 bits (80386 
EDX:EAX 
32 bits 
EAX 
EDX 

Unsigned division does not require careful attention to flags. The following examples illustrate signed division, which can be more complex.
.DATA
mem16 SWORD 2000
mem32 SDWORD 500000
.CODE
.
.
.
; Divide 16bit unsigned by 8bit
mov ax, 700 ; Load dividend 700
mov bl, 36 ; Load divisor DIV 36
div bl ; Divide BL 
; Quotient in AL 19
; Remainder in AH 16
; Divide 32bit signed by 16bit
mov ax, WORD PTR mem32[0] ; Load into DX:AX
mov dx, WORD PTR mem32[2] ; 500000
idiv mem16 ; DIV 2000
; Divide memory 
; Quotient in AX 250
; Remainder in DX 0
; Divide 16bit signed by 16bit
mov ax, WORD PTR mem16 ; Load into AX 2000
cwd ; Extend to DX:AX
mov bx,421 ; DIV 421
idiv bx ; Divide by BX 
; Quotient in AX 4
; Remainder in DX 316
If the dividend and divisor are the same size, signextend or zeroextend the dividend so that it is the length expected by the division instruction. See “Extending Signed and Unsigned Integers,” earlier in this chapter.
The instructions introduced so far in this chapter access numbers at the byte or word level. The logical, shift, and rotate instructions described in this section access individual bits in a number. You can use logical instructions to evaluate characters and do other text and screen operations. The shift and rotate instructions do similar tasks by shifting and rotating bits through registers. This section reviews some applications of these bitlevel operations.
The logical instructions AND, OR, and XOR compare bits in two operands. Based on the results of the comparisons, the instructions alter bits in the first (destination) operand. The logical instruction NOT also changes bits, but operates on a single operand.
The following list summarizes these four logical instructions. The list makes reference to the “destination bit,” meaning the bit in the destination operand. The terms “both bits” and “either bit” refer to the corresponding bits in the source and destination operands. These instructions include:
Instruction 
Sets Destination Bit If 
Clears Destination Bit If 


AND 
Both bits set 
Either or both bits clear 
OR 
Either or both bits set 
Both bits clear 
XOR 
Either bit (but not both) set 
Both bits set or both clear 
NOT 
Destination bit clear 
Destination bit set 
Note 
Do not confuse logical instructions with the logical operators, which perform these operations at assembly time, not run time. Although the names are the same, the assembler recognizes the difference.
The following example shows the result of the AND, OR, XOR, and NOT instructions operating on a value in the AX register and in a mask. A mask is any number with a pattern of bits set for an intended operation.
mov ax, 035h ; Load value 00110101
and ax, 0FBh ; Clear bit 2 AND 11111011
; 
; Value is now 31h 00110001
or ax, 016h ; Set bits 4,2,1 OR 00010110
; 
; Value is now 37h 00110111
xor ax, 0ADh ; Toggle bits 7,5,3,2,0 XOR 10101101
; 
; Value is now 9Ah 10011010
not ax ; Value is now 65h 01100101
The AND instruction clears unmasked bits — that is, bits not protected by 1 in the mask. To mask off certain bits in an operand and clear the others, use an appropriate masking value in the source operand. The bits of the mask should be 0 for any bit positions you want to clear and 1 for any bit positions you want to remain unchanged.
The OR instruction forces specific bits to 1 regardless of their current settings. The bits of the mask should be 1 for any bit positions you want to set and 0 for any bit positions you want to remain unchanged.
The XOR instruction toggles the value of specific bits on and off — that is, reverses them from their current settings. This instruction sets a bit to 1 if the corresponding bits are different or to 0 if they are the same. The bits of the mask should be 1 for any bit positions you want to toggle and 0 for any bit positions you want to remain unchanged.
The following examples show an application for each of these instructions. The code illustrating the AND instruction converts a “y” or “n” read from the keyboard to uppercase, since bit 5 is always clear in uppercase letters. In the example for OR, the first statement is faster and uses fewer bytes than cmp bx, 0. When the operands for XOR are identical, each bit cancels itself, producing 0.
;AND example  converts characters to uppercase
mov ah, 7 ; Get character without echo
int 21h
and al, 11011111y ; Convert to uppercase by clearing bit 5
cmp al, 'Y' ; Is it Y?
je yes ; If so, do Yes actions
. ; Else do No actions
.
yes: .
;OR example  compares operand to 0
or bx, bx ; Compare to 0
jg positive ; BX is positive
jl negative ; BX is negative
; else BX is zero
;XOR example  sets a register to 0
xor cx, cx ; 2 bytes, 3 clocks on 8088
sub cx, cx ; 2 bytes, 3 clocks on 8088
mov cx, 0 ; 3 bytes, 4 clocks on 8088
On the 80386/486 processors, the BSF (Bit Scan Forward) and the BSR (Bit Scan Reverse) instructions perform operations like those of the logical instructions. They scan the contents of a register to find the firstset or lastset bit. You can use BSF or BSR to find the position of a set bit in a mask or to check if a register value is 0.
The 8086based processors provide a complete set of instructions for shifting and rotating bits. Shift instructions move bits a specified number of places to the right or left. The last bit in the direction of the shift goes into the carry flag, and the first bit is filled with 0 or with the previous value of the first bit.
Rotate instructions also move bits a specified number of places to the right or left. For each bit rotated, the last bit in the direction of the rotate operation moves into the first bit position at the other end of the operand. With some variations, the carry bit is used as an additional bit of the operand. Figure 4.2 illustrates the eight variations of shift and rotate instructions for 8bit operands. Notice that SHL and SAL are identical.
Figure 4 . 2 Shifts and Rotates
All shift instructions use the same format. Before the instruction executes, the destination operand contains the value to be shifted; after the instruction executes, it contains the shifted operand. The source operand contains the number of bits to shift or rotate. It can be the immediate value 1 or the CL register. The 8088 and 8086 processors do not accept any other values or registers with these instructions.
Starting with the 80186 processor, you can use 8bit immediate values larger than 1 as the source operand for shift or rotate instructions, as shown here:
shr bx, 4 ; 9 clocks, 3 bytes on 80286
The following statements are equivalent if the program must run on the 8088 or 8086 processor:
mov cl, 4 ; 2 clocks, 3 bytes on 80286
shr bx, cl ; 9 clocks, 2 bytes on 80286
; 11 clocks, 5 bytes total
Masks for logical instructions can be shifted to new bit positions. For example, an operand that masks off a bit or group of bits can be shifted to move the mask to a different position, allowing you to mask off a different bit each time the mask is used. This technique, illustrated in the following example, is useful only if the mask value is unknown until run time.
.DATA
masker BYTE 00000010y ; Mask that may change at run time
.CODE
.
.
.
mov cl, 2 ; Rotate two at a time
mov bl, 57h ; Load value to be changed 01010111y
rol masker, cl ; Rotate two to left 00001000y
or bl, masker ; Turn on masked values 
; New value is 05Fh 01011111y
rol masker, cl ; Rotate two more 00100000y
or bl, masker ; Turn on masked values 
; New value is 07Fh 01111111y
You can use the shift and rotate instructions (SHR, SHL, SAR, and SAL) for multiplication and division. Shifting a value right by one bit has the effect of dividing by two; shifting left by 1 bit has the effect of multiplying by two. You can take advantage of shifts to do fast multiplication and division by powers of two. For example, shifting left twice multiplies by four, shifting left three times multiplies by eight, and so on.
Use SHR (Shift Right) to divide unsigned numbers. You can use SAR (Shift Arithmetic Right) to divide signed numbers, but SAR rounds negative numbers down — IDIV always rounds negative numbers up (toward 0). Division using SAR must adjust for this difference. Multiplication by shifting is the same for signed and unsigned numbers, so you can use either SAL or SHL.
Multiply and divide instructions are relatively slow, particularly on the 8088 and 8086 processors. When multiplying or dividing by a power of two, use shifts to speed operations by a factor of 10 or more. For example, these statements take only four clocks on an 8088 or 8086 processor:
sub ah, ah ; Clear AH
shl ax, 1 ; Multiply byte in AL by 2
The following statements produce the same results, but take between 74 and 81 clocks on the 8088 or 8086 processors. The same statements take 15 clocks on the 80286 and between 11 and 16 clocks on the 80386. (For a discussion about instruction timings, see “A Word on Instruction Timings” in the Introduction.)
mov bl, 2 ; Multiply byte in AL by 2
mul bl
As the following macro shows, it’s possible to multiply by any number — in this case, 10 — without resorting to the MUL instruction. However, such a procedure is no more than an interesting arithmetic exercise, since the additional code almost certainly takes more time to execute than a single MUL. You should consider using shifts in your program only when multiplying or dividing by a power of two.
mul_10 MACRO factor ; Factor must be unsigned
mov ax, factor ; Load into AX
shl ax, 1 ; AX = factor * 2
mov bx, ax ; Save copy in BX
shl ax, 1 ; AX = factor * 4
shl ax, 1 ; AX = factor * 8
add ax, bx ; AX = (factor * 8) + (factor * 2)
ENDM ; AX = factor * 10
Here’s another macro that divides by 512. In contrast to the previous example, this macro uses little code and operates faster than an equivalent DIV instruction.
div_512 MACRO dividend ; Dividend must be unsigned
mov ax, dividend ; Load into AX
shr ax, 1 ; AX = dividend / 2 (unsigned)
xchg al, ah ; XCHG is like rotate right 8
; AL = (dividend / 2) / 256
cbw ; Clear upper byte
ENDM ; AX = (dividend / 512)
If you need to shift a value that is too large to fit in one register, you can shift each part separately. The RCR (Register Carry Right) and RCL (Register Carry Left) instructions carry values from the first register to the second by passing the leftmost or rightmost bit through the carry flag.
This example shifts a multiword value.
.DATA
mem32 DWORD 500000
.CODE
; Divide 32bit unsigned by 16
mov cx, 4 ; Shift right 4 500000
again: shr WORD PTR mem32[2], 1 ; Shift into carry DIV 16
rcr WORD PTR mem32[0], 1 ; Rotate carry in 
loop again ; 31250
On the 80386 and 80486 processors, an alternate method for multiplying quickly by constants takes advantage of the LEA (Load Effective Address) instruction and the scaling of indirect memory operands. By using a 32bit value as both the index and the base register in an indirect memory operand, you can multiply by the constants 2, 3, 4, 5, 8, and 9 more quickly than you can by using the MUL instruction. LEA calculates the offset of the source operand and stores it into the destination register, EBX, as this example shows:
lea ebx, [eax*2] ; EBX = 2 * EAX
lea ebx, [eax*2+eax] ; EBX = 3 * EAX
lea ebx, [eax*4] ; EBX = 4 * EAX
lea ebx, [eax*4+eax] ; EBX = 5 * EAX
lea ebx, [eax*8] ; EBX = 8 * EAX
lea ebx, [eax*8+eax] ; EBX = 9 * EAX
Scaling of 80386 indirect memory operands is reviewed in “Indirect Memory Operands with 32Bit Registers” in Chapter 3. LEA is introduced in “Loading Addresses into Registers” in Chapter 3.
The next chapter deals with more complex data types — arrays, strings, structures, unions, and records. Many of the operations presented in this chapter can also be applied to the data structures covered in Chapter 5, “Defining and Using Complex Data Types.”