Classic Computer Magazine Archive COMPUTE! ISSUE 51 / AUGUST 1984 / PAGE 120

MACHINE LANGUAGE

Jim Butterfield, Associate Editor

Decimal Mode

Part 2

Decimal mode is quite useful in arithmetic programming such as game scoring and simple accounting. It has other uses, too—for example, in converting binary numbers to decimal for output. It also has certain bugs, pitfalls, and conventions.

Bugs And Pitfalls

Don't depend on the Zero and Negative (Z and N) flags immediately following a decimal addition (ADC) and subtraction (SBC). If you really need them, perform a data transfer (for example, TAX) to insure the flags are set correctly. The Carry flag is correct and has its usual meaning after the addition or subtraction.

Remember that decimal mode uses only the ADC and SBC instructions. The increment and decrement instructions (INX, INY, INC, DEX, DEY, DEC) behave in binary; and comparisons (CMP, CPX, CPY) are based as usual on binary values.

Programmers using machines with interrupt sequences must be careful of decimal mode. The interrupt can clear decimal mode with CLD (Clear Decimal); when the interrupt code finishes with RTI, the status register will be restored and decimal mode will be reinstated if it was in effect before. On Commodore machines, the interrupt sequences do not include a CLD instruction; in this case, the interrupt should be locked out using a SEI (Set Interrupt Disable) before going into decimal mode.

The VIC-20 and Commodore 64 have a use­ful feature: Registers may be preset before a SYS call. Addresses $030C, $030D, $030E, and $030F (decimal 780 to 783) contain values that will be transferred to registers A, X, Y, and the status register at the time of a SYS. When the machine language program returns to BASIC, these same addresses will contain the contents of the respective register. In other words, we could POKE 780,65 followed by a SYS; and the machine language program would start running with a value of $41 (decimal 65) in the A register.

What does this mean to decimal mode? Here's the possible danger: If the wrong value is contained in address 783, it will be transferred to the status register at the time of a SYS. An uncontrolled value might set decimal mode, or even worse, set the interrupt disable flag. To make things worse, these flags will not be restored when we return to BASIC. They will be neatly stored in 783, but BASIC will resume with the flags in an unworkable state. There goes BASIC.

It's probably wise to leave address 783 alone. If it worries you, POKE 783, 0 before giving a SYS command.

Conventions

We can handle fractions in decimal arithmetic. It's best to do this by using an "assumed decimal point." In other words, we will work dollar values as an integer number of pennies, and kilometers as integer values of meters. It's easier to stick in the decimal point at output time.

Negative numbers are a little tricky. We can use a scheme similar to that in binary numbers: That is, the "high bit" of a number represents the sign. This, however, splits positive and negative unevenly: A two-byte number will range from a low of –2000 (value 8000) up to + 7999. If you use this method, don't forget that the N flag isn't dependable after an addition or subtraction and that you'll need to take an extra step to test the flag.

A better technique is called "tens complement" and it's been used in many household devices such as counters on tape recorders. We understand that a reading of 9994 really means–6. If we want to use this technique, we might choose to try to split positive and negative more evenly, so that a two-byte number would range from –5000 to + 4999. In this case, we must remember not to use the N bit, but instead compare the high byte to 50 hex. If it is higher, the number is negative.

If "tens complement" is used, remember to invert a negative number at the time of printing. I find that the easiest way to do this is to subtract it from 0000 so that 9993 becomes 0007.

Multiplication

To multiply two decimal numbers we are almost forced to resort to repeated addition. As we go from one decimal digit to the next, we must "shift" either the multiplier or the product: This is a binary shift-four-places. It's awkward and we can quickly see why binary is preferred.

There's an elegant way to multiply a decimal number by a binary value, or by a fixed amount. We can use what I call a "decimal shift."

A binary shift multiplies a number by two. We can do the same thing with a decimal number by adding it to itself. Thus, to multiply by two we add the number to itself (in decimal mode). To multiply by four we multiply by two, twice. To multiply by five, we multiply by four and add the original number.

A Multiplication Example

We'll have the computer (PET, VIC, or 64) output a table of multiples of the number 5. (Two would be too easy.)

		;set value to one
033C A2 01		LDX   #$01
033E 8E B0 03	STX   LOW
0341 CA		DEX		
0342 8E B1 03 	STX   MED		
0345 8E B2 03	STX   HIGH
0348 8E B6 03	STX   COUNT
		; copy the number
034B A0 02	  LOOP LDY   #$02	
034D B9 B0 03    CP LDA   LOW, Y
0350 99 B3 03       STA   COPY, Y
0353 88             DEY
0354 10 F7          BPL    CP
		;multiply by four
0356 A2 02          LDX   #$02
0358 18         FP  CLC
0359 A0 FD          LDY   #$FD
035B 78             SEI
035C F8             SED
035D B9 B3 02   TP  LDA   HIGH-255, Y
0360 79 B3 02       ADC   HIGH-255, Y
0363 99 B3 02       STA   HIGH-255, Y
0366 C8             INY
0367 D0 F4          BNE   TP
0369 CA             DEX
036A D0 EC          BNE   FP
		;add original value
036C A0 FD          LDY   #$FD
036E 18             CLC
036F B9 B3 02  AP   LDA   HIGH-255, Y
0372 79 B6 02       ADC   COPY-253, Y
0375 99 B3 02	 STA   HIGH-255, Y
0378 C8		 INY
0379 D0 F4		 BNE   AP
037B D8		 CLD
037C 58             CLI
		;print the number
037D A0 02		 LDY   #$02
037F B9 B0 03   LP  LDA   LOW, Y
0382 4A             LSR   A
0383 4A             LSR   A
0384 4A             LSR   A
0385 4A             LSR   A
0386 09 30          ORA   #$30
0388 20 D2 FF       JSR   $FFD2
038B B9 B0 03       LDA   LOW, Y
039E 29 0F          AND   #$0F
0390 09 30          ORA   #$30
0392 20 D2 FF       JSR   $FFD2
0395 88		 DEY
0396 10 E7          BPL   LP
		;print RETURN and loop
0398 A9 0D          LDA   #$0D
039A 20 D2 FF       JSR   $FFD2
039D EE B6 03       INC   COUNT
03A0 AE B6 03       LDX   COUNT
03A3 E0 07          CPX   #$08
03A5 D0 A4          BNE   LOOP
03A7 60             RTS

Note the peculiar addressing in lines 035D to 0363 and again in 036F to 0375. We need to have a positive-incrementing index (in this case Y), since we must start our addition at the low-order value, LOW, and work upwards. We cannot use the obvious method of starting at zero and testing to see when we have done all three values, because we want the carry flag to be preserved; CPY (Compare Y) would destroy the previous value of the carry and our addition wouldn't work right.

If you'd rather enter the program from BASIC, here's the same program in DATA statements. It will work on all Commodore machines.

Multiples Of 5

100 DATA 162, 1, 142, 176, 3, 202, 142, 177, 3
110 DATA 142, 178, 3, 142, 182, 3, 160, 2
120 DATA 185, 176, 3, 153, 179, 3, 136, 16, 247
130 DATA 162, 2, 24, 160, 253, 120, 248, 185, 179, 2
140 DATA 121, 179, 2, 153, 179, 2, 200, 208, 244, 202
150 DATA 208, 236, 160, 253, 24, 185, 179, 2, 121, 182, 2
160 DATA 153, 179, 2, 200, 208, 244, 216, 88, 160, 2
170 DATA 185, 176, 3, 74, 74, 74, 74, 9, 48, 32, 21 0, 255
180 DATA 185, 176, 3, 41, 15, 9, 48, 32, 210, 255, 136, 16
190 DATA 231, 169, 13, 32, 210, 255, 238, 182, 3, 174, 182, 3
200 DATA 224, 8, 208, 164, 96
300 FOR J = 828 TO 935
310 READ X : T = T + X
320 POKE J, X
330 NEXT J
340 IF T<>13479 THEN STOP
350 SYS 828