Classic Computer Magazine Archive A.N.A.L.O.G. ISSUE 64 / SEPTEMBER 1988 / PAGE 45

BCD N U

The rather cryptic title of this article can be translated as "BinaryCoded Decimal and You." Last month we discussed some routines for interconverting strings of numeric ASCII characters and their binary representations as integers.

I know you're eager to dive into
the sample program for today,
but I'm going to hold you back
a little longer.


    This time we tackle another commonly used method for storing numbers in computers: binary-coded decimal, or BCD for short. After I explain the BCD representation, well see how to change an ASCII string into a BCD storage format. I also have some examples of how to do arithmetic with numbers stored in BCD from, and some traps you can fall into if you don't keep your wits about you.

Binary-Coded Decimal
    Look at the bit patterns for digits 0-9 shown in Table 1. Notice that they range form 0000 to 1001. The point here is that we need only four bits to represent any one of the ten decimal digits. You no doubt recall that the standard byte contains a grand total of eight bits. If we think of subdividing a byte, we could make a duplex with each unit containing four bits. A 4-bit unit is sometimes referred to as a "nybble" (a small byte-get it?). I've seen it spelled more conventionally as nibble, but I'll use the "y" so the noncomputer whizzes who read this will think I'm talking about something really obscure and hence important.
    Since we can store the binary representation of any one decimal digit in each nybble, the largest value that could be stored in a single byte this way is 99. This corresponds to a bit pattern of 1001 in each nybble; the entire storage contains 10011001. This two-digit-per-byte data storage method is the infamous binarycoded decimal.
    There are two ways to interpret a bit pattern of 10011001. In pure hexadecimal, it is $99, which corresponds to decimal 153. But if we think of it as two decimal digits, that bit pattern means decimal 99. We need some way to tell the computer which meaning we have in mind at any given time.
    Doing arithmetic on BCD numbers is different from processing binary numbers also. In binary, adding 1 to a byte containing the value 00001001 ($09) produces the value 00001010 ($0A). In BCD, adding 1 to 00001001 (09) would result in 00010000 (10). Similarly, adding 1 to 10011001 in hex terms produces 10011010 ($99 to $9A). But in BCD we should wind up with 00000000 in this byte and the carry flag set to indicate that a higher order byte must be incremented. This is a fancy way of saying that 99 plus 1 equals 100.
    The 6502 microprocessor in the Atari 8-bit computers can perform either decimal or binary arithmetic, thereby handling either of the two conditions from the previous paragraph. Bit 3 in the processor status register controls whether decimal mode (bit set) or binary mode (bit cleared) is selected. So far, we've performed only binary arithmetic operations, so most of our programs have begun with a CLD (CLear Decimal mode) instruction. To choose decimal mode, use the SED (SEt Decimal mode) instruction. You will get very strange results if the decimal flag isn't set the way you think it is; so it's always a good idea to explicitly select the desired mode.
    Actually, it's a little worse than "very strange." If you try to do things like print to the screen when the decimal flag is set, you can wind up in computer never-never land, with a coldstart being the only way back. Always clear decimal mode with the CLD instruction when you've finished your decimal arithmetic operations.

Interconverting ASCII and BCD
    Today's example is similar in format to last month's discussion of how to interconnect ASCII and binary storage formats for integers. Listing 1 contains two macros in MAC/65 format that should be appended to your MACRO.LIB file using the line numbers shown. Similarly, Listing 2 contains a pair of subroutines called by these macros; append Listing 2 to your SUBS.LIB file.
    My MACRO.LIB file is now an even 100 single-density sectors long. If you're using a RAM disk for assemblies, this is only a minor nuisance. However, reading a file that large from a physical disk each time you do an assembly takes a long time, and it doesn't do your disk drive any good. You may want to think about splitting the MACRO.LIB file into several smaller library files, perhaps grouped logically by function. You can do this any way you like, and just .INCLUDE the ones you need for your current project. Be sure to keep the equates needed by the macros accessible (and unduplicated). In fact, you might just collect all the equates into a separate EQUATES.LIB file. I'll leave the details of the MACRO.LIB dissection to each of you.
    The two new macros, and their corresponding subroutines, are named ASC2BCD and BCD2ASC. These complement the ASC2INT and INT2ASC routines from the previous Boot Camp. ASC2BCD takes a string of up to six numeric characters and converts it into a three-byte BCD number. Not surprisingly, BCD2ASC takes a three-byte BCD number and transforms it right back into a printable ASCII string. The macros themselves do some error checking and use parameters to handle ASCII strings and BCD numbers stored at any address, while their subroutine partners do most of the real work.

The non-computer
whizzes who read this will think
I'm talking about something
really obscure and important.


ASCII to BCD
    We'll start at the beginning. Please turn your attention to the ASC2BCD macro in Listing 1. ASC2BCD expects two parameters, the address of the ASCII string to convert, and the address where the resulting three-byte BCD number is to be stashed. An error message appears if the number of parameters is not two (Lines 8130-8140).
    This macro begins just like the ASC2INT macro from last time. Lines 8160-8220 copy the characters from the input string at the address specified in parameter % 1 to a work address labeled ASCII. The ASCII address was defined in Listing 1 from last month as $0690. The input string must terminate with an end-of-line character ($9B). In our sample program today, the numeric string to convert is read from the keyboard using our INPUT macro, which automatically guarantees that an EOL character will be present.
    Line 8230 calls the VALIDASC subroutine from last month, which makes sure that all the characters in the string are in fact digits in the range 0-9. If not, the carry flag is set in the subroutine to indicate an error. Line 8240 handles this condition by simply short-circuiting around the rest of the macro code. The main program that invoked this macro handles the error condition, as we'll see a little later. I don't have any provision for handling negative numbers.
    Subroutine VALIDASC retains only the lower four bits from the ASCII character. That is, if you entered the digit "7" at the keyboard, the ASCII value is $37, and VALIDASC changes this back into a plain "7" after confirming that it is a legal entry.
    After the conversion, the BCD number resides at a work location called NUM, defined as $0696 last month. Lines 8280-8350 copy the BCD result to the desired output address specified in parameter % 2.
    After all this monkey business, we wind up with a string of characters at address ASCII which looks exactly like what we typed at the keyboard. Let's pretend we typed the number "7239." Our goal is to convert the input string, now typed in five bytes like this (showing both nybbles in each byte):

07 02 03 09 9B

into BCD format stored in three bytes like this:

00 72 39

Notice that this numeric storage format is different from the low-byte/high-byte format used for binary integers.
    Line 8250 of Listing 1 calls the ASC2BCD subroutine in Listing 2 to handle the details of the conversion. Now please direct your attention to Listing 2.
    First we need to know how many input digits to convert to BCD. When we get to today's example in Listing 3, you'll see that the value we need was stored in a work address labeled CHARCTR. The value in CHARCTR includes the EOL character, so it is one larger than the actual number of digits in the input number. Lines 4720-4740 of Listing 2 set up the Xregister as an offset for the characters for the BCD bytes. Here's the conversion plan.
    We'll begin by zeroing the three bytes where the BCD result will be stored. Lines 4760-4790 handle this task. The conversion step will begin with the least significant (rightmost) digit in the entered ASCII string. This number becomes the low-order nybble in the least significant (rightmost) BCD byte (Lines 4810-4820).
    If the ASCII string to be converted contains an odd number of digits, the highorder nybble of one of the the BCD bytes will remain zero. This should be apparent to you. Line 4830 in Listing 2 points to the next ASCII character, which is destined to go into the high-order nybble of the current BCD byte. Line 4840 checks to see if we've reached the end of the ASCII string yet. If not, fetch the contents of the next ASCII byte (Line 4850). Remember that we've already changed this from the original ASCII value to the value of the digit itself (e.g., $37 was changed to 7).
    Lines 4860-4890 shift this number four bits to the left, thereby relocating it to the high-order nybble of the accumulator. Line 4900 combines the result with the low-order nybble from the previous ASCII digit, and the completed BCD byte (now containing two digits) is stored back where it belongs (Line 4910). Lines 4920-4960 check to see if were done with the ASCII string yet and loop back to continue if not.
    This discussion is a little confusing. You might find it illuminating to use your debugger to trace through a stepwise processing of a sample input number after entering Listing 3, and see how the ASC2BCD subroutine does its thing.
    I know you're eager to dive into the sample program for today, but I'm going to hold you back a little longer. The sample program goes through a bunch of BCD arithmetic examples, which we'll get to in a moment. But while the details of the ASCII-to-BCD conversion are fresh in your mind, I want to tackle the reverse process. Bear with me.

The 6502 microprocessor knows
how to do arithmetic on numbers
stored in both binary and
decimal modes. There are a
few differences.



BCD to ASCII
    I'm sure you can figure out what we must do to change a number stored in BCD format into a printable ASCII string. There are two basic steps. First, split the high and low nybbles of each byte in the BCD number into separate bytes in the output string. And second, convert the digits into their corresponding ASCII values. As an additional cosmetic nicety, we'll also convert any leading zeros to leading blanks.
    The BCD2ASC macro begins at line 8540 of Listing 1, and the complementary BCD2ASC subroutine starts at line 5150 of Listing 2. The macro again requires two parameters, the address of the BCD number to be converted and the address where the resulting ASCII string should be stored. Three bytes at address NUM and six bytes at address ASCII are again used as work locations. Lines 8580-8630 of the macro copy the contents of the BCD number into work location NUM. The subroutine BCD2ASC is then called. Lines 8650-8710 then copy the resulting string from address ASCII into the address specified in parameter %2. Lines 8720-8740 tack an EOL character on the end so the string can be printed.
    This time the conversion proceeds from left to right (high order to low order). In the BCD2ASC subroutine, I've set aside one byte (called ZEROBLANK, defined in Line 5570 of Listing 2) to indicate whether a zero digit is to be represented as a zero ASCII character ($30) or as a blank ($20). ZEROBLANK is initially set to $20 in Lines 5160-5170 so as to print leading zeros as blanks. However, as soon as a non-zero digit is encountered, ZEROBLANK is set to $30 so that zeros in the middle of the number appear properly.
    Line 5210 gets the first (leftmost) BCD digit, which is saved temporarily on the program stack (Line 5220). (The Xregister is used as an offset into the BCD number, and the Y-register as an offset into the ASCII string.) The high nybble is moved into the low nybble with a series of four right shifts; this is the opposite of the four ASLs we used in the ASC2BCD process. If the result is a zero, Lines 5290-5300 store the current value of ZEROBLANK into the next position in the output string. If the digit is not a zero, Lines 5330-5360 convert the digit to ASCII by adding $30 to it, store the result in the output string, and set the value of ZEROBLANK to an ASCII zero.
    Lines 5380-5400 point to the next output character, retrieve the BCD byte, and strip off the four most significant bits. This leaves just the low nybble, which is the second of the two digits in the BCD byte. Then the same activities are performed as for the first digit, depending on whether the digit is a zero or not (Lines 5410-5440). After processing all three BCD bytes, we wind up with a printable ASCII string. Voila.

BCD Arithmetic
    Now for the interesting part. The 6502 microprocessor knows how to do arithmetic on numbers stored in both binary and decimal modes. There are a few differences you should keep in mind, and Listing 3 will help you out.
    The program in Listing 3 asks you to enter a number up to six digits long, verifies that you entered only digits, converts the string of ASCII characters to a threebyte BCD number, and performs some representative arithmetic operations in both BCD and decimal mode. The results from each operation are printed on the screen in a little table. Let's walk though Listing 3 now.
    Line 160 pulls in the macros from our library file. Be sure to change this statement if you are using a real disk drive instead of the D8: RAM disk, or if you segmented the MACRO.LIB file as I suggested earlier. Some work variables are defined in Lines 280-310. BCD is the home of the BCD number. CHARCTR contains the number of ASCII characters you entered (including the EOL character). INBUF is an input buffer for the number you enter, and OUTBUF is an output buffer for the printable ASCII result.
    As usual, the executable code begins at address $5000. Lines 520-590 clear decimal mode (for now), clear the screen, prompt you to enter a number, store the number at INBUF, and store the number of characters you entered at CHARCTR. Lines 650-760 set up the column and row headings for the output table; the text strings to be printed are stored in Lines 2350-2480. Line 830 converts the input string in INBUF to BCD representation at address BCD. Well have to repeat this after each sample calculation to make sure the BCD number starts out the same way every time. If there's an error in the BCD conversion, the carry flag will be set and the program terminates due to Lines 840-850.
    The program has four sample calculations: increment the lowest BCD byte; add decimal 25 to the BCD number; add hex 25 to the BCD number; and add the contents of the middle BCD byte to the whole BCD number. Each calculation is done in both binary and decimal modes. I suggest you try this program with several sample entries, to see what happens. Press return after the output appears to try another number. Four interesting numbers to try are 0, 1234, 999 and 7239. On the off chance that you don't really want to spend the time typing in the listings, I've included tables to the output you would see for each of these test cases (Tables 2-5).

The simplest arithmetic operation
you can do in 6502 assembly
language is to increment the
contents of a byte. The opcode
for this is, of course, INC



Incrementing

    The simplest arithmetic operation you can do in 6502 assembly language is to increment the contents of a byte. The opcode for this is, of course, INC. Lines 980-1030 increment the least significant byte of the BCD number (BCD$2) in binary mode, and Lines 1070-1130 do the same in decimal mode. Notice the SED instruction in Line 1070 to set the decimal mode flag. Line 1090 clears the flag immediately after the arithmetic is done to avoid problems with subsequent operations. After each operation, the resulting value at address BCD is converted to ASCII at address OUTBUF and printed on the screen.
    The first line of Table 2 shows that INC works just fine when the target number is 0, giving the expected result of 1 in each case. Table 3 shows that INC works fine for the number 1234 also. But wait! An input value of 999 gives the bizarre result of 99:. Something similar happens in Table 5 with 7239. How can this be?
    Well, for Table 4, BCD + 2 contains "99;" which is incremented to "9A." Converting to ASCII gives two bytes, containing $39 (prints as a 9) and $3A (prints as a colon, :). Hmmmm. We really wanted the BCD number "99" to increment to "00;" setting the carry flag to indicate that the next higher order byte should also be incremented. It appears that the INC instruction has the same effect in decimal mode as it does in binary. Moral: Don't use INC to add 1 to a BCD number. Instead, go through the cumbersome motions of actually adding 1.

Adding 25
    Okay, so let's add something to a BCD number. Lines 1210-1330 add an immediate value of 25 decimal to the BCD number you entered in binary mode. Lines 1370-1490 do the same in decimal mode. These routines use a subroutine called INCREMBCD (Lines 2630-2750 of Listing 3) to handle the case where the carry flag is set after the addition, so that the next higher order byte must be incremented (by adding 1 to it, of course). This in itself might reset the carry flag, so that the highest order BCD byte also has to be incremented. These operations should make sense to you by now.
    Let's do it. Now look at the second output line in Tables 2-5 to see how our sample numbers respond. A problem is immediately apparent in Table 2. Adding 25 to 0 gave 19, not 25. Why? Well, the hex equivalent of 25 is $19. Last month we added decimal 25 (using an ADC #25 instruction) to a number stored as binary, went through the binary-to-ASCII conversion, and got the right answer. But we've scrambled our conventions here. We added a decimal number (stored internally as hex, of course) to a BCD number, using binary mode, and converted the presumed BCD result to ASCII for printing. It's not surprising that the wrong result shows up.
    The same thing happens with all the other input numbers. The weird characters in Tables 3 and 4 appear again because the addition results have gone out of the legal 0-9 BCD range, into values which print as other ASCII characters. Check out your table of hex codes for ASCII characters if you don't believe me.

The correct method for adding an
immediate value to a stored BCD
number is to use the desired
decimal digits for the immediate
number, but tell the computer that
it's a hex number.



Adding $25
    The correct method for adding an immediate value to a stored BCD number is to use the desired decimal digits for the immediate number, but tell the computer that it's a hex number. That is, to add decimal 25 to a BCD number, use an ADC #$25 instruction. The third output line in each table shows that this approach does indeed produce the result of adding 25. The initial entry of 1234 fortuitously gives the correct answer in either binary or decimal modes (Table 3). However, an entry of 999 works right only in decimal mode (Table 4). In binary mode, the computer sets the carry flag when the byte's contents exceed $FF, not $99 as it does in decimal.

Adding Two Stored Numbers
    The final line of each table shows the result of adding the middle byte of the BCD number to the entire number, just to show how things work when you add together two stored values. Table 2 correctly shows no output for this line, since 0 + 0 = 0, which we print as all blanks. For the other three cases, the correct answer is always obtained when the decimal flag is set, and only in some cases (e.g., Table 4) when in binary mode.

So What?
    Now you know more about binarycoded decimal than you ever dreamed possible. But why should you care? Burrow back through your archives to the yellowed, brittle pages of ANALOG #43. The Boot Camp in that issue discussed floating point numbers and mathematics in the Atari. Floating point numbers use the BCD representation as a compact way to stuff several digits of precision into a minimum number (six) of bytes. A special notation is used to keep track of the decimal point, exponent and negative sign in floating point numbers. BCD turns out to be a pretty efficient storage format for base-10-type numbers, and most computers use some form of BCD for floating point storage.
    You may recall that the main alternative character-coding method in common computer use is called EBCDIC (pronounced ebb-see-dick), used mainly by IBM mainframe computers. That acronym stands for "Extended Binary-Coded Decimal Interchange Code." See? You can run, but you just can't hide from binarycoded decimal.
    There's another advantage. In today's example program, we converted BCD numbers to ASCII strings and printed them on the screen. However, you could also take each BCD digit, convert it to the Atari internal character code by ANDing it with $10 (as opposed to $30, which converts it to ASCII), and poke the result directly into the screen RAM for the current display. This is simpler and faster than printing on the screen, and the visual result is the same. A good example of this technique can be found in James Hague's Streamliner from ANALOG #56. See the right column of page 37 in that issue.

Promise
    I promise: no more hard-core computing for awhile. We'll get back to some graphics (warm know how to draw circles?), sound effects and real-time clocks (how about a metronome program?), and maybe even the kernel of an adventure program; a simple vocabulary parser. Stay tuned.

Table 1. ASCII Codes for
Decimal Characters
Character ASCII
Value
 
Binary
Values
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

Table 2. Sample Output From Input
Number of 0


Binary Mode Decimal Mode
INC
1
1
Add 25 19
19
Add $25 25
25
Add 2nd
Byte


Table 4. Sample Output From Input
Number of 999


Binary Mode Decimal Mode
INC 99:
99:
Add 25 9;2
1018
Add $25 9; > 1024
Add 2nd
Byte

9:2

1008
Table 3. Sample Output From Input
Number of 1234


Binary Mode Decimal Mode
INC 1235 1235
Add 25 124= 1253
Add $25 1259 1259
Add 2nd
Byte

1246

1246
Table 5. Sample Output From Input
Number of 7239


Binary Mode Decimal Mode
INC 723:
723:
Add 25 7252 7258
Add $25 725> 7264
Add 2nd
Byte

72:;

7311



LISTING 1: ASSEMBLY

8010 ;
8020 ;*******************************
8030 ;
8040 ;ASC2BCD macro
8050 ;
8060 ;Usage:  ASC2BCD chars,number
8070 ;'chars' is address of ASCII
8080 ; string to convert,ending w/ EOL
8090 ;'number' is address of BCD
8100 ; representation of the string
8110 ;
8120     .MACRO ASC2BCD
8130       .IF X0<>2
8140       .ERROR "Error in ASC2BCD"
8150       .ELSE
8160       LDX #255
8170 @ASCLOOP2
8180       INX
8190       LDA %1,X
8200       STA ASCII,X
8210       CMP #EOL
8220       BNE @ASCLOOP2
8230       JSR VALIDASC
8240       BCS @DONE2
8250       JSR ASC2BCD
8260       BCS @BCDERROR
8270       LDX #0
8280 @ASCLOOP3
8290       LDA NUM,X
8300       STA %2,X
8310       INX
8320       CPX #3
8330       BNE @ASCLOOP3
8340       CLC
8350       BCC @DONE2
8360 @BCDERROR
8370        PRINT  CONVERTMSG2
8380       SEC
8390 @DONE2
8400       .ENDIF
8410     .ENDM
8420 ;
8430 ;***************************
8440 ;
8450 ;BCD2ASC macro
8460 ;
8470 ;Usage: BCD2ASC number,chars
8480 ;'number' is address of BCD
8490 ; number to convert
8510 ;'chars' is address of resulting
8520 ; ASCII string, ending with EOL
8530 ;
8540     .MACRO BCD2ASC
8550       .IF X.802
8560       .ERROR "Error in BCD2ASC"
8570       .ELSE
8580       LDA %1+1
8590       STA NUM
8600       LDA %1+1
8610       STA NUM+1
8620       LDA %1+2
8630       STA NUM+2
8640       JSR BCD2ASC
8650       LDX #255
8660 @BCDLOOP
8670       INX
8680       LDA ASCII,X
8690       STA %2,X
8700       CPX #5
8710       BNE @BCDLOOP
8720       INX
8730       LDA #EOL
8740       STA %2,X
8750       .ENDIF
8760     .ENDM


LISTING 2: ASSEMBLY

4600 ;
4610 ;*******************************
4620 ;
4630 ;subroutine ASC2BCD
4640 ;called by ASC2BCD macro
4650 ;
4660 ;converts string of ASCII digits
4670 ;at address ASCII to a 3-byte
4680 ;binary-coded decimal represen-
4690 ;tation at address NUM
4700 ;
4710 ASC2BCD
4720     LDX CHARCTR ;how many chars
4730     DEX         ;to convert?
4740     DEX
4750     LDY #2
4760     LDA #0      ;zero 3 bytes
4770     STA NUM     ;where BCD value
4780     STA NUM+1   ;will go
4790     STA NUM+2
4800 NXTDIG
4810     LDA ASCII,X ;get next char
4820     STA NUM,Y   ;low BCD digit
4830     DEX         ;point to next
4840     BMI BCDDONE ;done yet?
4850     LDA ASCII,X ;get new char
4860     ASL A       ;shift into
4870     ASL A       ;high nybble
4880     ASL A
4890     ASL A
4900     ORA NUM,Y   ;becomes high
4910     STA NUM,Y   ;BCD digit
4920     DEX         ;point to prev.
4930     BMI BCDDONE ;done yet?
4940     DEY         ;Point to next
4950     CLC         ;BCD digit
4960     BCC NXTDIG  ;go get it
4978 BCDDONE
4980     CLC         ;all done, so
4990     RTS         ;leave
5000 CONVERTMSG2
5010     .BYTE "ASCII to BCD cone
5020     .BYTE "version error",EOL
5030 ;
5040 ;*******************************
5050 ;
5060 ;subroutine BCD2ASC
5070 ;called by BCD2ASC macro
5080 ;
5090 ;converts 3-byte BCD number at
5100 ;address NUM to a 6-byte ASCII
5110 ,string at address ASCII
5120 ;leading zeros are changed to
5130 ;leading blanks
5140 ;
5150 BCD2ASC
5160     LDA #S20    ;init leading
5170     STA ZEROBLANK ;char to blank
5180     LDX #0      ;pointer to digit
5190     LDY #0      ;pointer to char
5200 NXTDIG2
5210     LDA NUM,X   ;get 1st digit
5220     PHA         ;stash on stack
5230     CLC
5240     LSR A       ;move high nybble
5250     LSR A       ;into low nybble
5260     LSR A
5270     LSR A
5280     BNE NONZERO1 ;equal to 0?
5290     LDA ZEROBLANK ;yes, set to
5300     STA ASCII,Y ;leading char
5310     BPL DOLOW   ;do low half
5320 NONZERO1
5330     ORA #$30    ;change to ASCII
5340     STA ASCII,Y ;add to string
5350     LDA #$30    ;set leading
5360     STA ZEROBLANK ;char to '0'
5370 DOLOW
5380     INY         ;aim at next char
5390     PLA         ;get BCD digit
5400     AND #$0F    ;keep low nybble
5410     BNE NONZERO2 ;equal to 0?
5420     LDA ZEROBLANK ;yes, set to
5430     STA ASCII,Y ;leading char
5440     BPL BCDDONE2 ;all done
5450 NONZERO2
5460     ORA #$30    ;conver to ASCII
5470     STA ASCII,Y ;add to string
5480     LDA #$30    ;set leading char
5490     STA ZEROBLANK ;to zero
5500 BCDDONE2
5510     INY         ;point to next
5520     INX         ;digit and char
5530     CPX #3      ;done 3 digits?
5540     BNE NXTDIG2 ;no, continue
5550     CLC         ;yes, all done
5560     RTS         ;exit
5570 ZEROBLANK .DS 1


LISTING 3: ASSEMBLY

0100 ;Example 1. Interconverting ASCII
0110 ;strings and BCD numbers
0120 ;
0130 ;by Karl E. Wiegers
0140 ;
0150     .OPT NO LIST,OBJ
0160     .INCLUDE #D8:MACRO.LIB
0170 ;
0180 ;-------------------------------
0190 ;
0200 ;store some work variables at
0210 ;$4FE8 so you can examine them
0220 ;if you like
0230 ;
0240 ;-------------------------------
0250 ;
0260     *=  $4FE8
0270 ;
0280 BCD .DS 3
0290 CHARCTR .DS 1
0300 INBUF .S 7
0310 OUTBUF .DS 7
0320 ;
0330 ;-------------------------------
0340 ;
0350 ;    PROGRAM STARTS HERE
0360 ;
0370 ;You'll be prompted to enter a
0380 ;number with 1-6 digits. This
0390 ;is stored at address INBUF.
0400 ;The BCD number produced is
0410 ;stored in 3 bytes starting at
0420 ;address BCD. Then several
0430 ;arithmetic operations are done
0440 ;in both binary and decimal mode,
0450 ;and a table of results is
0460 ;printed out.
0470 ;
0480 ;-------------------------------
0490 ;
0500     *=  $5000
0510 ;
0515 START
0520     CLD         ;binary mode!
0530     JSR CLS     ;clear screen
0540      PRINT  PROMPT ;getsinput
0550      POSITION  2,2 ;number
0560      INPUT  0,INBUF
0570     LDX #$00    ;get number of
0580     LDA ICBLL,X ;chars entered
0590     STA CHARCTR
0600 ;
0610 ;------------------------------
0620 ; lay out the table of results
0630 ;------------------------------
0640 ;
0650      POSITION 12,5
0660      PRINT TITLE
0670      POSITION 12,6
0680      PRINT HYPHENS
0690      POSITION 2,8
0700      PRINT INCRE
0710      POSITION 2,10
0720      PRINT DEC25
0730      POSITION 2,12
0740      PRINT HEX25
0750      POSITION 2,14
0760      PRINT ADDBYTE
0770 ;
0780 ;------------------------------
0790 ;convert string to BCD, abort if
0800 ;have a conversion problem
0810 ;------------------------------
0820 ;
0830     ASC2BCD  INBUF,BCD
0840    BCC NOPROBLEM
0850    JMP END
0860 ;
0870 ;------------------------------
0880 ;First line: increment the BCD
0890 ;number in binary and decimal
0900 ;modes; be sure to set back to
0910 ;binary before doing anything
0920 ;else!
0930 ;reconvert from input string to
0940 ;BCD after each operation
0950 ;------------------------------
0960 ;
0970 NOPROBLEM
0980     CLD
0990     INC BCD+2
1000      BCD2ASC  BCD,OUTBUF
1010      POSITION  14,8
1020      PRINT  OUTSUF
1030      ASC28CD  INBUF,BCD
1040 ;
1050 ;increment in decimal mode
1060 ;
1070     SED
1080     INC BCD+2
1090     CLD
1100      BCD2ASC  BCD,OUTBUF
1110      POSITION  29,8
1120      PRINT  OUTBUF
1130      ASC2BCD  INBUF,BCD
1140 ;
1150 ;------------------------------
1160 ;Second line: add 25 to the BCD
1170 ;number in binary and decimal
1180 ;modes
1190 ;------------------------------
1200 ;
1210     CLD
1220     CLC
1230     LDA BCD+2
1240     ADC #25
1250     STA BCD+2
1260     BCC NOINC1
1270     JSR INCREMBCD
1280 NOINC1
1290     CLD
1300      BCD2ASC  BCD,OUTBUF
1310      POSITION  14,10
1320      PRINT  OUTBUF
1330      ASC2BCD  INBUF,BCD
1340 ;
1350 ;add 25 in decimal mode
1360 ;
1370     SED
1380     CLC
1390     LDA BCD+2
1400     ADC #25
1410     STA BCD+2
1420     BCC NOINC2
1430     JSR INCREMBCD
1440 NOINC2
1450     CLD
1460      BCD2ASC  BCD,OUTBUF
1470      POSITION 29,10
1480      PRINT  OUTBUF
1490      ASC25CD  INBUF,BCD
1500 ;
1510 ;-------------------------------
1520 ;Third line: add hexadecimal 25
1530 ;to the BCD number in binary and
1540 ;binary modes
1550 ;-------------------------------
1560 ;
1570     CLD
1580     CLC
1590     LDA BCD+2
1600     ADC #$25
1610     STA BCD+2
1620     BCC NOINC3
1630     JSR INCREMBCD
1640 NOINC3
1650     CLD
1660      BCD2ASC  BCD,OUTBUF
1670      POSITION  14,12
1680      PRINT  OUTBUF
1690      ASC26CD  INBUF,BCD
1700 ;
1710 ;add $25 in decimal mode
1720 ;
1730     SED
1740     CLC
1750     LDA BCD+2
1760     ADC #$25
1770     STA BCD+2
1780     BCC NOINC4
1790     JSR INCREMBCD
1800 NOINC4
1810     CLD
1820      BCD2ASC  BCD,OUTBUF
1830      POSITION  29,12
1840      PRINT  OUTBUF
1850      ASC28CD  INBUF,BCD
1860 ;
1870 ;-------------------------------
1880 ;Fourth line: add second byte
1890 ;of BCD number to the entire
1900 ;number, in binary and decimal
1910 ;modes. If number was 1-2 digits
1920 ;long, will just add zero
1930 ;-------------------------------
1940 ;
1950 ;ADD 2ND BYTE TO 3RD - BINARY
1960     CLD
1970     CLC
1980     LDA BCD+1
1990     ADC BCD+2
2000     STA BCD+2
2010     BCC NOINC5
2020     JSR INCREMBCD
2030 NOINC5
2040     CLD
2050      BCD2ASC  BCD,OUTBUF
2060      POSITION  14,14
2070      PRINT  OUTBUF
2080      ASC2BCD  INBUF,BCD
2090 ;
2100 ;add 2nd byte to total - decimal
2110 ;
2120     SED
2130     CLC
2140     LDA BCD+1
2150     ADC BCD+2
2160     STA BCD+2
2170     BCC NOINC6
2180     JSR INCREMBCD
2190 NOINC6
2200     CLD
2210      BCD2ASC  BCD,OUTBUF
2220      POSITION  29,14
2230      PRINT  OUTBUF
2240 END
2244      INPUT  B,INBUF
2248     JMP START
2250 ;
2260 ;------------------------------
2270 ;text lines for prompt and for
2280 ;output table
2290 ;------------------------------
2300 ;
2310 PROMPT
2320     .BYTE "Enter a number "
2330     .BYTE "up to 6 digits "
2340     .BYTE "long:",EOL
2350 TITLE
2360     .BYTE "Binary Mode    "
2370     .BYTE "Decimal Mode",EOL
2380 HYPHENS
2390     .BYTE "-----------    "
2400     .BYTE "------------",EOL
2410 INCRE
2420     .BYTE "INC",EOL
2430 DEC25
2440     .BYTE "Add 25",EOL
2450 HEX25
2460     .BYTE "Add $25",EOL
2470 ADDBYTE
2480     .BYTE "Add 2nd byte",EOL
2490 ;
2500 ;-------------------------------
2510 ;don't forget the subroutines!
2520 ;-------------------------------
2530 ;
2540     .INCLUDE #D8:SUBS.LIB
2550 ;
2560 ;*******************************
2570 ;subroutine do handle carry if
2580 ;adding to the third BCD byte
2590 ;went above 99; can't increment,
2600 ;so must add 1 to higher order
2610 ;bytes as needed
2620 ;
2630 INCREMBCD
2640     SED         ;still in decimal
2650     CLC
2660     LDA #1      ;add i to second
2670     ADC BCD+1   ;BCD byte
2680     STA BCD+1   ;and store
2690     BCC NOMOREINC ;cause carry?
2700     CLC
2710     LDA #1      ;yes, so add 1 to
2720     ADC BCD     ;first BCD byte
2730     STA BCD     ;and store
2740 NOMOREINC
2750     RTS        ;all done, exit