Machine Code Programming – Multiplication and Division

Publication: Syntax
Pub Details: Syntax/Syntax ZX80 v2 n6 (Volume 2, Issue 6)
Date: June 1981
Pages: 5-7

Last time we tried Sinclair‘s machine language (ML) routine for determining available memory. It added 11 to the stack pointer and returned an answer to BASIC–easy stuff. Subtraction in binary is also easy; subtract column-by-column and borrow when necessary.

Multiplication and division require more thought and attention to detail. One or two commands will not multiply 2 numbers, as in BASIC. Still, ML multiplication and division routines are straightforward. If you don’t understand some terms here, don’t worry–just follow the logic and use the program as a recipe.

Refer to the books by Rodnay Zaks (Programming the Z80, 2d ed. pp. 113-136) and Kathe Spracklen (Z-80 and 8080 Assembly Language Programming, pp. 60-65) for more details. Zaks shows sample code and Spracklen gives math examples.

Glance through this assembly listing that converts degrees Fahrenheit to Celsius using the formula C=5(F-32)/9. (This problem would be easier to solve using BASIC, but we’ll use ML to see how it’s done). The first column is the memory address where beginning bytes of instructions reside. If the instruction needs more bytes, they will be stored in successive locations. The second column lists hexadecimal program codes (you can enter these using Matthew Johnson‘s Hex Monitor, May 81). Column three labels subroutines–marking parts of the program. Assembly language commands are in the fourth and fifth columns. The last column contains comments, preceded by semicolons. Like REM statements, comments identify functions of the lines.

Note the repetitive look of commands in column five. Good ML programming breaks problems into small tasks the computer can do.

In decimal multiplication, you shift each partial product 1 column to the left for each left shift you make in the multiplier:

(multiplicand)        32
(multiplier)        x 15
(partial product)    160
(partial product)    32
(product)            480

Mentally, you do 1×32, and shift 1 place left to multiply by 10.

Look closely and you’ll see the routine shifts the multiplier in register C to the right. Shift either way, but stick to one direction throughout the problem, as in this decimal example:

SHIFT LEFT          SHIFT RIGHT
    23                  23
  x 12                x 12
    46                  23
   23                   46
   276                 276

In binary multiplication all partial products are either the multiplicand or a row of zeros, since 1 times any number is that number and 0 times any number is 0. (Examine the multiplication, and remember, 1+1=10 in binary. Carry the 1 to the next column, as in decimal addition.)

At the end of SUBT (subtract), we have taken the Fahrenheit value from a POKEd memory address (see PEEK-POKE story this issue), subtracted 32, and left the multiplicand (F-32) in the E half of the DE register pair, a temporary Z80 storage location.

MULMR loads the multiplier (5) and sets a bit-counter in register B (again, a temporary storage location) that we count down with each shift. Z80’s use a special command, DJNZ PROCESS, that decrements B and jumps to the named process if B is not 0. When B is 0, the Z80 knows it’s done.

MULT multiplies binary numbers bit-by-bit, as you multiply decimal numbers column-by-column. MULT shifts the multiplier right into the carry bit and adds the result to the HL register each time the carry bit is 1. The loop continues until the bit-counter reads 0 (at the other end of the multiplier) meaning all bits have been counted. Meanwhile, the HL register pair accumulates a running partial product total.

 C-register  carry bit
   11001101 0  (beginning)
 0>01100110 1  (incr prod & shift)
 0>00110011 0  (shift only)

Dividing is like multiplying, except that the divisor (number you divide by) is subtracted from the dividend (number you divide into). DIV performs binary long division after DIV66 loads the divisor (9) into D, zeros E and resets the bit-counter, B. Each time the computer subtracts divisor from dividend, it adds 1 to the quotient and shifts the new dividend and quotient left 1 bit.

The following examples show how long division is the same in both decimal and binary:

Long division in decimal (2163/21) and binary
Long division in decimal (2163/21) and binary

If the subtraction yields a negative result, the program must restore the quotient total and the dividend to their previous values prior to proceeding. Finally the answer is in L and the remainder in H; the next-to-last command zeros the remainder.

This routine returns integer quotients to BASIC. Use C9 as the last command to get back to BASIC from machine language.

You can convert Fahrenheit values from 32 to 491 degrees. Beyond these limits, the program fails because the result in the L register is outside its 0-255°C range. Another program must handle negative or larger results.

To run the program, you need to POKE a Fahrenheit value from BASIC. For 32-255°F, POKE 17357, val of F and POKE 17358,0. For 256-491 F, POKE 17357,val of F-256 and POKE 17358,1. After POKEing the values, use PRINT USR(17359) to see the result.

You can write a whole BASIC program to use this ML program. First, enter the memory protect program, p.8. Use 1 for K and 51 for N. This saves 51 bytes at the top of memory from encroachment from BASIC programs. DO NOT TYPE NEW ON 4K ROMS. Load Hex Monitor and change the starting address to be 17407 (top of 1K RAM) minus the bytes you reserved, 51, in line 90. Input the hex values for the conversion program. Then load or type in a BASIC program to ask for Fahrenheit values, POKE the ML memory locations, call the USR routine at 17407-51+2 (to avoid the 2 bytes where you POKEd in the F values) and display the results.

ASSEMBLY LISTING (4K only; see p.12)

43CD         00080        ORG   43CDH
43CD 0000 00090 TEMP DEFW 0
43CF 2ACD43 00100 SUBT LD HL,(TEMP) ;LOAD FAHREN
43D2 1600 00110 LD D,0 ;CLEAR D
43D4 1E20 00120 LD E,20H ;LOAD SUBTRAHEND
43D6 A7 00130 AND A ;CLEAR CARRY
43D7 ED52 00140 SBC HL,DE ;FAHRENH-32
43D9 EB 00150 EX DE,HL ;RESULT INTO DE
43DA 210000 00160 LD HL,0 ;CLEAR HL
43DD 0E05 00170 MULMR LD C,5H ;LOAD MULTIPLIER
43DF 0608 00180 LD B,8H ;INITIALIZE COUNTER
43E1 CB39 00190 MULT SRL C ;SHFT RGT TO CARRY
43E3 3001 00200 JR NC,NOADD ;TEST CARRY
43E5 19 00210 ADD HL,DE ;(FAHRENH-32)+RESULT
43E6 EB 00220 NOADD EX DE,HL
43E7 29 00230 ADD HL,HL ;DOUBLE SHIFT
43E8 EB 00240 EX DE,HL
43E9 10F6 00250 DJNZ MULT ;LOOP UNTIL B=0
43EB 1609 00260 DIV66 LD D,9H ;LOAD DIVISOR
43ED 1E00 00270 LD E,0 ;CLEAR E
43EF 0608 00280 LD B,8H ;INITIALIZE COUNTER
43F1 A7 00290 DIV AND A ;CLEAR CARRY
43F2 ED52 00300 SBC HL,DE ;DIVID-DIVIS
43F4 23 00310 INC HL ;QUOT=QUOT+1
43F5 F2FA43 00320 JP P,NOA22 ;TEST POSITIVE
43F8 19 00330 ADD HL,DE ;RESTORE IF NECESS
43F9 2B 00340 DEC HL ;QUOT=QUOT-1
43FA 29 00350 NOA22 ADD HL,HL ;DOUBLE SHIFT
43FB 10F4 00360 DJNZ DIV ;LOOP UNTIL B=0
43FD 2600 00370 LD H,0 ;SET RMNDR ZERO
43FF C9 00380 RET