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 useful 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