Jim Butterfield, Associate Editor
Part II
NUMERIC
OUTPUT
This is the second in a three-part series on techniques of handling numeric displays or printouts in machine language.
Preparing decimal output can be done in a number of ways. The methods for converting binary integers to decimal can be summarized by direction: right-to-left or left-to-right. In both cases, there is usually a need to perform division. And don't forget that each digit must be converted to ASCII before it is output.
No matter which way we do the job, we need to plan the output format. A one-byte number might require three decimal digits to be printed (e.g., 255), but a two-byte number might need five digits (e.g., 65535). It's often a good idea to plan to output a fixed number of digits, since numbers may need to be printed neatly into columns or onto specific parts of the screen. We might also find it desirable to suppress leading zeros on a number so that 00307 becomes 307, with leading spaces.
Right-To-Left
The method goes something like this: divide by ten. The remainder is the rightmost digit. If the quotient is non-zero, repeat. Thus, a binary value of 287 is calculated: divide by 10, remainder 7; divide quotient 28 by 10, remainder 8; divide quotient 2 by 10, remainder 2. The quotient becomes zero at this point, so we have the three digits - 2, 8, and 7.
The digits come out backwards, however. In the above example, we can't print the 7 the moment we calculate it, since we must work out two earlier digits. That's not a problem, since the digits can be placed into a buffer area - or on the stack, for that matter.
Right-to-left is attractive because it automatically finds the number of digits that need to be printed; the procedure stops when a quotient of zero is reached. You can immediately spot numbers that are too big. It's also very easy to insert leading spaces to fill out the number to any desired length. You'll need a good divide-by-ten routine, of course.
Left-To-Right
This method takes a little more effort to set up, but generates digits in the "normal" order, which allows you to output them directly. Zero suppression adds a little extra code.
We must start by assuming the number of digits that we wish to output. Let's say, for example, that we expect up to three digits. We would follow roughly the following procedure:
Set FACTOR to 100;
Divide the number by FACTOR;
The quotient is the next digit;
Take the remainder, set FACTOR to 10, and repeat;
Then set FACTOR to 1 and repeat; or for that matter, the remainder from the last calculation will be your last digit.
Divide the number by FACTOR;
The quotient is the next digit;
Take the remainder, set FACTOR to 10, and repeat;
Then set FACTOR to 1 and repeat; or for that matter, the remainder from the last calculation will be your last digit.
To convert 287, we divide by 100; the quotient of 2 is our first digit. Take the remainder (87) and divide by 10; the quotient of 8 is the next digit. Finally, the remainder of 7 is our last digit whether or not we divide it by 1.
We can achieve this without a formal division routine; repeated subtraction will work efficiently enough for most purposes. We might change our algorithm to read:
Set FACTOR to 100;
Set COUNTER to 0;
If the number is greater than or equal to FACTOR, then subtract FACTOR from the number,. add 1 to COUNTER, and repeat this step;
COUNTER now contains the first digit; you may print it.
Now set FACTOR to 10, COUNTER to 0, and repeat.
Set COUNTER to 0;
If the number is greater than or equal to FACTOR, then subtract FACTOR from the number,. add 1 to COUNTER, and repeat this step;
COUNTER now contains the first digit; you may print it.
Now set FACTOR to 10, COUNTER to 0, and repeat.
Our example of 287 would have 100 subtracted from it until it reached 87. The counter would have counted 2 subtractions, so we can send the digit 2 to output.
The various factors (1000, 100, 10, 1, or whatever is needed) may be stored in a table for quick reference rather than calculated. Using true divi sion would be faster than our subtraction algorithm. But since we'll never need to subtract more than nine times for each digit (and since we're likely to spend much more time delivering the output digit to its destination), it's not much of a worry.
Mathematics fiends will tell you that the left-to-right procedure may be easily extended to generate decimal fractions. Useful, but only if you arc using binary numbers with fractional parts in the first place.
An Example
Let's do some very quick code to output a dozen numbers from memory in decimal. We'll use the left-to-right method. Zero suppression won't be used. Address FFD2 will be used for output (PET/CBM/VIC/C64 compatible).
OUTPUT LDX #$00 (number counter)
STX COUNT
NXNUM LDA $0350,X (get mem value)
LDY #$02 (2+1 digits)
LOOP CMP TABLE,Y
BCC DONE
SBC TABLE,Y
INC COUNT
BNE LOOP
DONE PHA (add seven)
LDA COUNT
ORA #$30
JSR $FFD2
LDA #$00
STA COUNT
PLA
DEY
BPL LOOP
LDA #$0D
JSR $FFD2
INX
CPX #$0A
BCC NXNUM
RTS
TABLE .BYTE 1,10,100
It's fun to do this in a practical example. Let's POKE it from BASIC:
100 DATA
162,0,142,144,3,189,80,3
110 DATA 160,2,217,132,3,144,8
120 DATA 249,132,3,238,144,3
130 DATA 208,243,72,173,144,3,9,48
140 DATA 32,210,255,169,0,141,144,3
150 DATA 104,136,16,225,169,13
160 DATA 32,210,255,232,224,10
170 DATA 144,210,96,1,10,100
200 FOR J=848 TO 902:READ X
210 T=T+X:POKE J,X
220 NEXT J
230 IF T<>6199 THEN STOP
300 SYS 848
110 DATA 160,2,217,132,3,144,8
120 DATA 249,132,3,238,144,3
130 DATA 208,243,72,173,144,3,9,48
140 DATA 32,210,255,169,0,141,144,3
150 DATA 104,136,16,225,169,13
160 DATA 32,210,255,232,224,10
170 DATA 144,210,96,1,10,100
200 FOR J=848 TO 902:READ X
210 T=T+X:POKE J,X
220 NEXT J
230 IF T<>6199 THEN STOP
300 SYS 848
It will take a few moments to POKE the program in place; after that, the decimal numbers come out with blinding speed (especially if you have cleared the screen so that there is no need for scrolling). The numbers, by the way, are the same values as in the DATA statements in line 100 and part of 110.
But there's more.
These are the conventional methods, and they have a number of variations that we haven't mentioned.
But there's a very fast and radically different method available on the 6502. It uses Decimal mode in an unusual way to generate decimal number output super fast.
More on that the next time around.