Classic Computer Magazine Archive A.N.A.L.O.G. ISSUE 62 / JULY 1988 / PAGE 76

From time to time I feel I need to remind you that computers were originally invented to perform computations. Yes, I know that spiffy visual and sound displays actually comprise the Atari appeal, but it all boils down to arithmetic in the end. today I'd like to talk about different ways to store numbers in the computer, and present some methods for interconverting characters and numbers. A consequence of this discussion is that soon we'll learn how to keep track of scores in such exciting games as "Attack of the Suicidal Road-Racing Aliens."

Boot Camp

by Karl Wiegers

Storing
Numbers

    So far, Boot Camp has focused on the most basic method for storing numbers in the computer, as binary integers. As you know, each byte of RAM can contain decimal value from 0 through 255 (hex $FF), based on the pattern of ones and zeros in the eight bits which make up the byte. If both positive and negative numbers must be accommodated, the most significant bit (bit 7) is reserved as a sign bit. If bit 7 is set, the number is negative; if cleared, the number is positive. This method leaves only seven data bits, so signed numbers ranging from -128 through + 127 can be represented in this way.
    Often we must deal with numbers larger than 255. We've used two adjacent bytes in RAM for this purpose, giving us 16 bits of unsigned data, or 15 bits for signed numbers. With 16 bits we can represent decimal numbers ranging from 0 through 65535 ($00 through $FFFF), or signed numbers from -32768 through +32767.
    This is all very fine, but it doesn't cover all our needs. Many numbers encountered in real life have fractional (decimal) parts, such as 7345.022. Obviously, the integer representation fails here. A more elaborate method for storing these so-called floating point numbers is used in the Atari, wherein each number occupies six bytes of RAM, regardless of its magnitude. Floating point storage uses a numeric representation called "binary-coded decimal" or BCD, which we'll discuss more next month. The Boot Camp column in issue 43 covered floating point numbers and computations in grim detail.
    Another problem arises when we wish to write a program in which the user enters a number that's used in subsequent calculations, or when a calculated number needs to be output to the screen or printer. A number like 7239 is stored internally in only two bytes, with the hex value $1C47. But to print "7239" on the screen requires four characters. To further complicate the issue, to make the character "7" appear we actually have to print the ASCII character code 55 ($37).
    (Things are even worse than they appear. The character with ASCII code 5 actually is stored internally in the Atari as character code 23. We won't worry about this today.)
    So, if we know that we want to print some numbers, we may want to choose another method for storing them internally, rather than using the standard two-byte integer. One possibility is to reserve one byte for each digit in our number. For the example of "7239," we would use four bytes. But what to put in each byte? We could, of course, simply store "7" in the first byte, "2" in the second, and so on. But we still couldn't print the number out this way. If we output an ASCII code of "7" to the screen, we get the same graphics symbol as you obtain by typing a control-G on the Atari keyboard (a diagonal slash). And you can't even print an ASCII code 7 on a printer. Sending an ASCII 7 to an Epson printer makes the printer's bell ring!
    Here's another option. Rather than storing "7" in the first byte, store the ASCII code for "7." Table 1 lists the ASCII codes and bit patterns for the digits 0-9. Note that each digit has an ASCII code equal to the digit value plus $30. Hence, if we printed a byte containing $37, a 7 would indeed appear on the screen or printer.
    There are a few problems associated with storing numbers in ASCII form. First, this requires more RAM than does the binary integer form. Also, you can't use the normal addition and subtraction operations, since they are designed to work with binary numbers.
    One good solution to the problem is to go ahead and store numbers in two-byte integer format, and simply convert them to an ASCII string before printing. For input, we must convert the ASCII string typed by the user into its binary numeric representation. The Atari operating system contains built-in routines to convert ASCII strings into their floating point form and back again. Unfortunately, no such routines exist to interconvert integers and ASCII strings. Today I'll present some macros and subroutines to perform all the necessary conversions.

Interconverting ASCII and Binary
    Today's example program lets you enter a number containing 1-5 digits at the keyboard. This number is checked to make sure it's valid and then is converted to a two-byte binary integer. Then, the value 25 is added to the integer, and the result is converted to ASCII format and printed on the screen. Let's dive in.
    Listing 1 contains three macros (MAC/65 format) that should be appended to your by-now-enormous MACRO.LIB file, using the line numbers shown. These macros use some bytes for work space, which I've defined in the equates in Lines 7380-7400. ASCII is the address where the ASCII string being converted is stored, and NUM is the address where the binary integer value for the number resides. Six bytes are reserved for ASCII, five for digits (the maximum value that works correctly is 65535) and one for an end-of-line character, $9B. The input routine uses the EOL character to know when to stop converting digits, and the output routine adds an EOL so the result can be printed on the screen. COUNTER is just a one-byte work variable.
    The first macro, ASC2INT, converts a numeric ASCII string into a two-byte binary integer. Parameter 1 is the address of the string to be converted (for example, an input buffer address), and parameter 2 is the address where the integer should be placed after conversion. This macro calls two subroutines that do most of the work, VALIDASC and ASC2INT (you can give a macro and a subroutine the same name). These and some other subroutines are found in Listing 2, which should be appended to your SUBS.LIB file using the line numbers shown.
    The second macro, INT2ASC, converts a binary integer into a printable ASCII string. Parameter 1 is the address of the integer to convert, and parameter 2 is the address where the ASCII string should be placed. As you might expect, this macro calls subroutine INT2ASC, which is also found in Listing 2.
    The ASCII string produced by the INT2ASC macro might not require all five characters reserved for it. For example, converting the number 43 to ASCII requires only two bytes for the character string. These digits are rightjustified in the five-character ASCII string produced, so the result produced from INT2ASC would have the form 00043.
    The LDGZERO macro (Lines 8110-8360 of Listing 1) can be used to convert any leading (that is, on the left) zeros into blanks for printing purposes. However, this macro does not left-justify the result in the five-character field, so if you printed the output ASCII string, you really would print three blanks in front of the 43. LDGZERO doesn't call any subroutines. It takes two parameters. The first is the address of the string to be processed, and the second is the maximum number of digits to examine for leading zeros.
    Now let's walk through a sample program and see how these conversion macros and subroutines do their stuff.

ASCII to Integer
    Please type in Listing 3, today's sample program. Note the .INCLUDE directives in lines 160 and 650. If your MACRO.LIB and SUBS.LIB files are not on a RAM disk, change the drive designation from D8: to the correct drive number.
    Almost every line in this example program is a macro call. This makes the source code much shorter and easier to understand than if we had to expand each procedure into its individual instructions. Also, notice my approach of using a macro in combination with one or more subroutines. The macro sets up the specifics of the particular operation, by virtue of addresses or values passed as parameters. I place the common details of the procedure into a subroutine wherever possible, using reserved pieces of RAM as general work variables. This method makes the resulting object code shorter and yet keeps the source code compact; a satisfactory compromise from my point of view.
    Line 380 of Listing 3 makes sure we are in binary mode for arithmetic operations (more about this next month), and Line 390 clears the display screen. Line 400 prints a message prompting you to enter a number containing from one to five decimal digits. Lines 410-420 store your response at address ENTRY, a block of six bytes reserved in Line 540. Line 430 invokes the macro to convert this ASCII string to a two-byte binary integer stored at address INTEGER (defined in Line 550). If the carry flag is set upon completing the macro execution, we know an error has taken place, so Line 440 simply branches to the end of the program.
    If we've ended up with a valid number at INTEGER, Line 450 adds 25 to that number. There's nothing magical about this; it's just a way to change the number you entered before I print it out again. Line 460 then converts that sum into an ASCII string at address ENTRY. Line 470 uses the LDGZERO macro to translate any leading zeros to blanks. You might try commenting Line 470 out and seeing what you get. Finally, Lines 480-520 print the resulting ASCII string on the screen and wait for you to press RESET. As usual, you can run this program from address $5000.
    Let's look at the ASCII to integer conversion in more detail. The first step is to make sure the user has entered a valid string of ASCII digits. Lines 7560-7640 in the ASC2INT macro definition in Listing 1 handle this chore. The loop simply looks through all the characters stored at the input buffer address (passed as parameter 1) until it finds an end-of-line character. Line 7600 stores each character in the appropriate position in the work variable called ASCII as the checking takes place. The subroutine VALIDASC is called to make sure the characters are all legitimate.
    I apologize for bouncing you around the listings, but now we need to examine subroutine VALIDASC, starting at Line 2980 in Listing 2. Lines 3100-3160 pluck one character at a time out of the ASCII string and check for an EOL. If the first character found is an EOL, then the user just pressed RETURN without entering anything, so Line 3160 branches to an error routine at label INVALID (Line 3280). An error message is printed and the carry flag is set to indicate to the calling macro that an error took place.
    The CHKASC routine beginning at Line 3190 tests whether each character has an ASCII value greater than $30 (decimal 0) and smaller than $3A (":," the first character past decimal 9). If not, control again branches to the INVALID routine. If the digit is okay, Lines 3240-3270 strip off the four high-order bits (thereby changing a $37 into a 7, for example), store the result back into the correct position in the ASCII string, and go get the next character.
    This procedure underscores my contention that the largest portions of most good computer programs are devoted to input/output routines and error checking. If we knew our users would make only valid entries, our programs could be much shorter. Never make such a shaky assumption, though!
    Okay, now the string at address ASCII consists only of valid digits, from one to five of them. The next step is to convert these digits into a binary number. The ASC2INT subroutine (Lines 3390-3700 of Listing 2) does the trick.
    Let's contemplate the philosophy of number representation once again. A decimal number like 7239 actually means to multiply 1000 by 7, multiply 100 by 2, multiply 10 by 3, multiply 1 by 9, and add all these products together. To transform a bunch of characters from the ASCII string "7239" into the binary equivalent, we must perform precisely these same operations. The ASC2INT subroutine does the work, with the help of another subroutine called MULT10 (Lines 3740-4020 of Listing 2). The MULT10 subroutine actually carries out the power of ten multiplications.
    We begin with the most significant digit in the string to be converted. In the case of "7239," this digit is a 7. Load the 7 into a byte and multiply by 10. This gives 70. Add the next digit in, yielding 72.
    Multiply this result by 10 to get 720 and add in the next digit, giving 723. Multiply this result by 10 to get 7230 and add in the final digit, to wind up with 7239. Of course, this answer doesn't look like 7239 in its binary representation. In binary it will look like 0001110001000111, and in hexadecimal it will be $1C47. There's one final twist. The Atari stores two-byte integers in low-byte/high-byte format, so decimal 7239 is represented in two adjacent bytes of RAM in the Atari as hexadecimal 4710. And you thought this stuff was going to be simple!
    Lines 3480-3510 of Listing 2 store a zero in the high-byte of our destination integer at address NUM and load the first (most significant) ASCII byte into the low-byte of NUM. If there's only one ASCII character, our conversion is complete; Lines 3520-3550 check for this condition. If the second character is indeed the EOL, Lines 3560-3570 clear the carry flag (our signal to the calling macro that all is well) and return. Otherwise, we go on to the NEXTDIGIT label to continue processing.
    The first step is to multiply this leftmost digit by 10. Subroutine MULT10 (Lines 3740-4020 of Listing 2) takes care of this for us. But how do we multiply using the 6502 processor? We've learned how to add and subtract using the ADC and SBC instructions. However, the 6502 contains no intrinsic multiplication or division instructions. You may recall that performing an ASL or Accumulator Shift Left operation is the same as multiplying the contents of a byte by two, and a LSR or Logical Shift Right operation divides the contents of a byte by two. Now we need to extend these concepts to handle a two-byte number and combine shift and add operations to perform integer multiplication.
    Remember that multiplication is really just a bunch of sequential additions. The 6502 gives us an easy way to multiply by 2. To multiply some number by 10 we could multiply it by 2; multiply by 2 again (net result is multiply by 4); add the original number back to the result (net result is multiply by 5); and multiply by 2 once again, to give a net result of multiplying by 10. This is precisely what happens in subroutine MULT10.
    One more point and then we'll look at the code. Suppose our original number is decimal 150, stored in a single byte as $96. If we multiply that by 2 we get 300 in decimal terms ($012D), but the maximum value that fits in a single byte is 255. Whatever shall we do? When an overflow like this takes place, the carry flag in the processor status register is set, and the original byte contains the value of 300 minus the maximum 255, or 45 ($2D). This carry value must be added to the high-byte of our two-byte number, which also underwent a left shift operation during the multiply by 2 step. Fortunately, the 6502's instruction set contains an instruction to handle all these details, the ROL or Rotate Left instruction.
    Each bit shifts to the next higher order position (i.e., to the left). The carry flag shifts into bit 0, and bit 7 shifts into the carry flag. If the carry flag is cleared, ROL is the same as an ASL, simply multiplying the byte's contents by 2. But if the carry is set, the ROL effectively multiplies by 2 and adds 1 to the original byte contents. Hence, a twobyte number can be multiplied by 2 simply by performing an ASL on the lowbyte, followed by an ROL on the highbyte to account for the carry flag. I can't believe you didn't think of this solution immediately. (Wiegers' First Law of Computing: Almost nothing you can do with a computer is difficult. Wiegers' Second Law of Computing: Almost nothing you can do with a computer is obvious.)
    In sum (pun intended), to multiply a two-byte binary integer by 2, you can simply perform an ASL operation on the low-byte, followed by an ROL operation on the high-byte.
    As promised, you may now look at the MULT10 subroutine in Listing 2. Lines 3820-3830 store the high-byte of the original number on the stack so we can grab it for the necessary addition. Line 3840 places the original low-byte into the accumulator. Lines 3850-3860 multiply the original number by 2, and Lines 3870-3880 do it again. Lines 3890-3930 add in the original number, so now we've effectively multiplied it by 5. (Notice that all intermediate results are stored back in the original location at NUM and NUM + 1.) Lines 3940-3950 complete the multiplication by 10. Lines 3960-4010 add in the next digit, as we discussed earlier.
    The loop in Lines 3580-3640 of Listing 2 (subroutine ASC2INT) continue this monkey business until an EOL character is reached in the ASCII string, at which point the carry flag is cleared to indicate success and control returns to the calling ASC2INT macro.
    We're now back at Line 7660 of Listing 1, in the middle of the ASC2INT macro. If the carry flag is set, there was a problem with the conversion, and an appropriate error message (which lives at Lines 3680-3700 of Listing 2) is printed. Otherwise, the binary result in address NUM is moved to the location specified in the second parameter in the ASC2INT call (Lines 7670-7700), and we're all done.

Integer to ASCII
    Whew! We finally got the simple number you entered stored in binary form. Now let's see how to go the other way. Our sample program adds 25 to whatever number you enter, just to change it. The INT2ASC macro converts the number whose address is supplied in parameter 1 to a character string stored at the address specified in parameter 2. The INT2ASC macro is in Lines 7820-8070 of Listing 1. Lines 7940-7970 just copy the number to be transformed to our work space at address NUM.
    Subroutine INT2ASC does all the work, creating a five-digit ASCII string of printable characters at address ASCII. Lines 7990-8050 copy this string, up through the EOL character, to the desired destination address in parameter 2. Subroutine INT2ASC is in Lines 4060-4590 of Listing 2. As with ASC2INT, this procedure is based on the fact that the position of a digit in a decimal number indicates the number of times a particular power of 10 must be added to zero to obtain that number. Algorithmically, it's easier to work backwards, performing multiple subtractions. You keep subtracting a particular power of 10 (10, 100, 1000 or 10000) from the integer in question until you obtain a negative result. The number of subtractions you can do before going negative is equal to the value of the digit in a specific column (tens, hundreds, thousands, or ten thousands).
    Here's an illustration. Begin with the familiar integer 7239. Let's set a counter equal to 0. How many times can you subtract 10000 from 7239 before you get a negative result? The answer is 0. Hence, the first of our five output digits (the ten thousands column) is 0. Next, how many times can you subtract 1000 from 7239 before obtaining a negative result? Seven, of course. Increment the counter for each successful subtraction. If your counter reaches 8 (representing 7239 minus 8000), the subtraction result is negative, and you know you've gone a digit too far. Add 1000 back in to get back to a positive number (7239-7000 = 239), and use the counter's value of 7 for the second digit in the output ASCII string.
    Continue this procedure until all powers of ten from 10000 to 10 have been done, and the remainder (the units column) is the fifth and final digit in the ASCII number. This is awkward to describe in words, but it actually makes some sense.
    We'll have to set bits 4 and 5 (ORA -$30) in our counter for the number of successful subtractions to convert it to the ASCII representation. If you walk through the commented INT2ASC subroutine you should understand this technique better. As you can see, it's a pretty cumbersome way to turn a two-byte binary integer into a five-character ASCII string, but it's just about the only way to do it. Lines 4500-4520 of the subroutine add an EOL to the end of the string so it can be printed properly using the PRINT macro, as done in the sample program of Listing 3.

Zero Zapper
    The INT2ASC conversion routine produces a five character ASCII string, plus an EOL character. If the integer being converted is smaller than 10000 decimal, the first ASCII digit will be a zero. The number of leading zeros equals five minus the number of decimal digits in the number being converted. Often, you wish to print a number with just significant digits shown, that is, without any leading zeros appearing. The LDGZERO macro, Lines 8110-8360 of Listing 1, replaces leading zeros with spaces.
    LDGZERO requires two parameters, the address of the string to be processed and the number of bytes to process before quitting. If a non-zero character (ASCII values $31-$39) is encountered, the routine terminates. The entire logic of this macro consists of looping through the bytes in the ASCII string replacing characters with ASCII code $30 (zero) with ASCII code $20 (blank or space character), until an end condition is satisfied.
    As you see when you run the program in Listing 3, leading blanks do "print," effectively shifting the significant digits to the right on the screen. You might want to write a macro or subroutine (or combination) to left justify a string by simply removing leading zeros, rather than translating them into blanks. That's not a hard exercise to do. While you're at it, why not write a routine to right justify a string in a field of some specified length? Don't forget error checking. What would happen if you tried to right justify a string of 11 characters in a field only 8 characters long? Oops.

Decimal Pointers
    I alluded to another numeric data storage format, binary-coded decimal. Next month we'll take a close look at BCD and see some routines for converting ASCII strings to BCD and vice-versa.

Table 1 .
ASCII / Character / Binary Equivalents.
Character ASCII Code Binary Value
0
$30
0000
1
$31 0001
2
$32 0010
3
$33 0011
4
$34 0100
5
$35 0101
6
$36 0110
7
$37 0111
8
$38 1000
9
$39 1001


Listing 1:
Assembly

7370 ;
7380 ASCII = $0690
7390 NUM =   $0696
7400 COUNTER = $0698
7410 ;
7420 ;*****************************
7430 ;
7440 ;ASC2INT macro
7450 ;
7460 ;Usage: ASC2INT chars,number
7470 ;
7480 ;'chars' is address of ASCII
7490 ; string to convert,ending w/ EOL
7500 ;'number' is address of integer
7510 ;
7520     .MACRO ASC2INT
7530       .IF %0<>2
7540       .ERROR "Error in ASC21NT"
7550       .ELSE
7560       LDX #255
7570 @ASCLOOP
7580       INX
7590       LDA %1,X
7600       STA ASCII,X
7610       CMP #EOL
7620       BHE @ASCL00P
7630       JSR VALIDASC
7640       BCS @DONE
7650       JSR ASC2INT
7660       BCS @ASCERROR
7670       LDA NUM
7680       STA %2
7690       LDA NUM+1
7780       STA %2+1
7710       CLC
7720       BCC @DONE
7730 @ASCERROR
7740        PRINT CONVERTMSG
7750       SEC
7760 @DONE
7770       .ENDIF
7780     .ENDM
7790 ;
7800 ;*******************************
7810 ;
7820 ;INT2ASC macro
7830 ;
7840 ;Usage: INT2ASC number,chars
7850 ;
7860 ;'number' is address of integer
7870 ;'chars' is address of resulting
7880 ; ASCII string, ending with EOL
7890 ;
7900     .MACRO INT2ASC
7910       .IF %<>2
7920       .ERROR "Error in INT2ASC"
7930       .ELSE
7940       LDA %1
7950       STA NUM
7960       LDA %1+1
7970       STA NUM+1
7980       JSR INT2ASC
7990       LDX #255
8000 @INTLOOP
8010       INX
8020       LDA ASCII,X
8030       STA %2,X
8040       CMP #EOL
8050       BHE @INTLOOP
8060       .ENDIF
8870     .ENDM
8080 ;
8090 ;*******************************
8100 ;
8110 ;LDGZERO Macro
8120 ;
8130 ;Usage: LDGZERO address,bytes
8140 ;
8150 ;'address' is beginning of ASCII
8160 ; string of digits
8170 ;'bytes' is max number of digits
8180 ; to check for leading zeros
8190 ;
8200     .MACRO LDGZERO
8210       .IF %0<>2
8220       .ERROR "Error in LDGZERO"
8230       .ELSE
8240       LDX #255
8250 @SUPZERO
8260       INX
8270       LDA %1,X
8280       CMP #$30
8290       BNE @LZDONE
8300       LDA #$20
8310       STA %1,X
8320       CPX #%2
8330       BNE @SUPZERO
8340 @LZDONE
8350       .ENDIF
8360     .ENDM

Listing 2:
Assembly

2950 ;
2960 ;*******************************
2970 ;
2980 ;subroutine VALIDASC
2990 ;called by ASC2INT,ASC2BCD macros
3000 ;
3010 ;makes sure all characters in
3020 ;string beginning at address
3030 ;ASCII are valid ASCII codes for
3040 ;numeric digits; looks until it
3050 ;hits an EOL; error message is
3060 ;printed and carry flag is set
3070 ;if an invalid char. is found
3080 ;
3090 VALIDASC
3100     LD# #0
3110 LOOPASC
3120     LDA ASCII,X ;get a char
3130     CMP #EOL    ;EOL?
3140     BHE CHKASC  ;no,go check it
3150     CPX #0      ;yes, 1st char?
3160     BEQ INVALID ;yes,null entry
3170     CLC         ;no, all done
3180     RTS         ;go back
3190 CHKASC
3200     CMP #$30    ;less than 0?
3210     BCC INVALID ;yes, no good
3220     CMP #$3A    ;greater than 9?
3230     BCS INVALID ;yes, no good
3240     AND #$0F    ;clear 4 hi bits
3250     STA ASCII,X ;save 4 lo bits
3260     INX         ;ready for next
3270     BCC LOOPASC ;char
3280 INVALID
3290      PRINT ASCERRMSG
3300     SEC        ;set carry to
3310     RTS        ;show an error
3320 ;
3330 ASCERRMSG
3340     .BYTE "Non-numeric "
3350     .BYTE "character found",EOL
3360 ;
3370 ;******************************
3380 ;
3390 ;subroutine ASC2INT
3400 ;called by ASC2INT macro
3410 ;
3420 ;converts string of ASCII digits
3430 ;at address ASCII to a 2-byte
3440 ;binary integer at address NUM;
3450 ;carry flag set if error
3460 ;
3470 ASC2INT
3480     LDA #0      ;zero hi byte
3490     STA NUM+1
3500     LDA ASCII   ;get first char
3510     STA NUM
3520     LDX #1      ;next char EOL?
3530     LDA ASCII,X ;next char EOL?
3540     CMP #EOL
3550     BNE NEXTDIGIT ;no, go on
3560     CLC         ;yes all done
3570     RTS
3580 NEXTDIGIT
3590     JSR MULT10  ;multiply by 10
3600     BCS ABORT   ;carry set? error
3610     INX
3620     LDA ASCII,X ;next char EOL?
3630     CMP #EOL
3640     BNE NEXTDIGIT ;no, go on
3650     CLC
3660 ABORT
3670     RTS         ;exit
3680 CONVERTMSG
3690     .BYTE "ASCII conversion"
3700     .BYTE " error ...",EOL
3710 ;
3720 ;******************************
3730 ;
3740 ;subroutine MULT10
3750 ;called by subroutine ASC2INT
3760 ;
3770 ;multiplies binary integer at
3780 ;address NUM and NUM+1 by 10
3790 ;and adds the next digit in
3800 ;
3810 MULT10
3820     LDA NUM+1   ;save high byte
3830     PHA
3840     LDA NUM     ;get low byte
3850     ASL NUM     ;multiply x 2
3860     ROL NUM+1
3870     ASL NUM     ;times 2 again
3880     ROL NUM+1
3890     ADC NUM     ;add to self to
3900     STA NUM     ;effectively
3910     PLA         ;multiply X 5
3920     ADC NUM+1
3930     STA NUM+1
3940     ASL NUM     ;times 2 again,
3950     ROL NUM+1   ;total is now x10
3960     LDA ASCII,X ;add in next char
3970     ADC NUM
3980     STA NUM
3990     LDA #0      ;adding 0 to high
4000     ADC NUM+1   ;byte just pulls
4010     STA NUM+1   ;in carry value
4020     RTS
4030 ;
4040 ;*******************************
4050 ;
4060 ;subroutine INT2ASC
4070 ;called by INT2ASC macro
4080 ;
4090 ;converts a 2-byte binary integer
4100 ;at address NUM to a string of
4110 ;ASCII digits at address ASCII
4120 ;
4130 INT2ASC
4140     LDY #0      ;pointer to table
4150     STY COUNTER ;of powers of 10
4160 NEKTDIGIT2
4170     LDX #0      ;digit counter
4180 SUBLOOP#0
4190     LDA NUM     ;get low byte
4200     SEC         ;subtract to byte
4210     SBC DECTABLE,Y ;of current
4220     STA NUM     ;power of 10
4230     LDA NUM+1   ;now subtract hi
4240     INY         ;byte of current
4250     SBC DECTABLE,Y ;power of 10
4260     BCC ADDITBACK ;if neg,restore
4270     STA NUM+1   ;save hi byte
4280     INX         ;digit counter
4290     DEY         ;point to to-byte
4300     CLC         ;of current power
4310     BCC SUBLOOP ;of 10 again
4320 ADDITBACK
4330     DEY         ;point to to byte
4340     LDA NUM     ;add to byte of
4350     ADC DECTABLE,Y ;power of 10
4360     STA NUM     ;back in
4370     TXA         ;convert digit
4380     ORA #$30    ;counter to ASCII
4390     LDX COUNTER ;and store at
4400     STA ASCII,X ;next position
4410     INC COUNTER
4420     INY         ;point to next
4430     INY         ;power of 10
4440     CPY #8      ;at end of table?
4450     BCC NEXTDIGIT2 ;no, go on
4460     LDA NUM     ;get units column
4470     ORA #$30    ;convert to ASCII
4480     LDX COUNTER ;store it
4490     STA ASCII,X
4500     INX
4510     LDA #EOL    ;add an EOL in
4520     STA ASCII,X ;next position
4530     RTS         ;all done
4540 ;
4550 DECTABLE
4560     .WORD 10000
4570     .WORD 1000
4580     .WORD 100
4590     .WORD 10

Listing 3:
Assembly

0100 ;Example 1. Interconverting ASCII
0110 ;strings and 2-byte integers
0120 ;
0130 ;by Karl E. Wiegers
0140 ;
0150     .OPT NO LIST,OBJ
0160     .INCLUDE #D8:MACRO.LIB
0170 ;
0180 -------------------------------
0190 ;    PROGRAM STARTS HERE
0200 ;
0210 ;You'll be prompted to enter a
0220 ;number with 1-5 digits. This
0230 ;is stored at address ENTRY.
0240 ;The binary integer produced
0250 ;is stored at address INTEGER.
0260 ;If the number is too large,
0270 ;missing (null entry) or has non-
0280 ;digits in it, you'll get an
0290 ;error message. 25 will be added
0300 ;to the value you entered, and
0310 ;the result will be converted to
0320 ;ASCII and printed on the screen
0330 ;
0340 ;-------------------------------
0350 ;
0360     *=  $5000
0370 ;
0380     CLD
0390     JSR CLS
0400      PRINT PROMPT
0410      POSITION 5,5
0420      INPUT 0,ENTRY
0430      ASC2INT ENTRY,INTEGER
0440     BCS END
0450      ADD INTEGER,25
0460      INT2ASC INTEGER,ENTRY
0470      LDGZERO ENTRY,5
0480      POSITION 2,8
0490      PRINT AFTER
0500      POSITION 5,10
0510      PRINT ENTRY
0520 END JMP END
0530 ;
0540 ENTRY .DS 6
0550 INTEGER .DS 2
0560 ;
0570 PROMPT
0580     .BYTE "Enter a number
0590     .BYTE "with 1-5 digits:",EOL
0600 AFTER
0610     .BYTE "After adding 25:",EOL
0620 ;
0630 ;-------------------------------
0640 ;
0650     .INCLUDE #D8:SUBS.LIB