COMPUTE! ISSUE 37 / JUNE 1983 / PAGE 250
MACHINE LANGUAGE
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.
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.
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
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.