!pr0
!tm2
!lm12
!rm75
Really Adding ASCII Strings................Bob Sander-Cederlof

Last month I promised a "reasonably useful" program to add two numbers together from ASCII strings.  I promised:

!lm+5
*  Callable from Applesoft, using &.

*  Automatic passing of string parameters.

*  Allow operands of unequal length.

*  Automatic alignment of decimal points.

*  Allow negative numbers.

*  Handle sums longer than operands.

*  Allow leading blanks on operands.

*  Allow operands and results up to 253 bytes long!

!lm-5
Okay!  It took me three days, but I did it!  Of course, the program has grown from 12 lines and 26 bytes of code to over 290 lines and over 450 bytes, too.

The program is now assembled to load at $9000, but you can choose other positions by changing line 1130.  I set HIMEM:36864 before doing anything else in the Applesoft program, and then BRUN B.STRING ADDER.

When B.STRING ADDER is BRUN, only the setup code in lines 1160-1220 is executed.  What this does is link in the ampersand (&) to the body of my program.  Once the "&" is linked, my program responds to a call like "& +$,A$,B$,C$" by adding the numeric values represented in ASCII in A$ and B$ and storing the sum as a string in C$.

When an &-line occurs, Applesoft branches to my line 1520.  Lines 1520-1600 check for the characters "+$," after the ampersand.  If you don't like those characters, change them to something else.  Anyway, if the characters do not match, you get SYNTAX ERROR.  If they do match, it is time to collect the three strings variables.

Lines 1620-1690 collect the three string variables.  The first two are the operands, the third is the result string.  I save the address and length of the actual data of the operand strings.  All I save at this point for the result string is the address of the variable descriptor.  I call the subroutine PARSE.STRING.NAME to check for a leading comma, search for the variable name, and store the length and address of the referenced string data.

Lines 1730-1860 scan each operand string in turn to find the decimal point position.  The routine SCAN divides a string at the decimal point (or where the decimal point would be if there was one), and returns in Y the number of characters to the left of the decimal point.  SCAN returns in X the count of the
!np
number of characters on the right end, including the decimal point.  I save the "digits.after" parts of both strings, and also the maxima of the two parts.  The maxima describe the result string (almost).

Lines 1900-2000 finish the description of the result string, by lengthening the integral (left) side by two characters.  These two characters allow for extension of the result by carry, and for representation of the sign of the result using ten's complement notation.  At this point I also clear the necessary bytes of the result to zero, so the buffer can be used as an accumulator.

Now comes the EASY part.  Lines 2040-2100 add each operand in turn to the buffer contents.  EASY.  Just call the subroutine ADD.TO.BUFFER, and it's done!  Don't worry, I'll amplify later.

In ten's complement notation, if the first digit is 0-4 the number is positive; if the first digit is 5-9, the number is negative.  For example, 1234 looks like 001234; -1234 becomes 998766.  Ten's complement means in decimal the same thing two's complement means in binary.  I can form the ten's complement by subtracting the number from a power of ten equal to the number of digits in the result.  In that example, 1000000-1234=998766.  Note that the ten's complement is equal to the nine's complement plus one.  (Since 10=9+1.)


Lines 2140-2410 convert the buffer contents from the ten's complement numeric notation back to ASCII.  Lines 2140-2180 set or clear the CARRY and TENS.FLAG sign bits according to the first digit in the buffer.  A negative number, with a first digit of 5-9, causes both of these variables to get a value of the form 1xxxxxxx.

Lines 2190-2360 scan through the number from right to left, making the ten's complement if the number was negative, and converting each digit to ASCII.  Lines 2370-2400 store a minus sign in the first digit position if the result is negative.

Line 2410 calls a subroutine to chop off leading zeros, and move the minus sign if there is one.  You may justifiably ask, "Why did you call a subroutine rather than use in-line code?"  Because when I wrote it in-line, the local labels stretched out too far from the major label STRADD and caused an assembly error.  Also, sometimes I use subroutines for clarity, even when the subroutine is only called once.

The final step is to pack the resulting string up and ship it to the result string variable.  Lines 2450-2590 do just that.  AS.GETSPA makes room at the bottom of string pool space, and AS.MOVSTR copies the string data.  C'est finis!

Lines 2640-3100 do the actual addition.  On entry, X is either 0 or 4, selecting either the first or second operand.  SETUP.OPERAND copies the string address into VARPNT, and retrieves the length of the string.  Lines 2690-2760 set or clear the TENS.FLAG and CARRY variables according to the sign of the operand.
!np
Lines 2780-2810 compute the position in the buffer at which the operand will be aligned properly.  We saved the size of the integral (left) side of the buffer in MAX.DIGITS.BEFORE.  That plus the lenght of the fractional side of the operand tells us where this operand aligns.  Since we are using ten's complement for negative numbers, rather than nine's complement, we don't have to worry about extending the fractional parts to the same length.  We can just start adding at the end of the current operand.  (In ten's complement form fractional extensions are zeros; in nine's complement form, the extension digits would all be nines.)

Lines 2830-3100 do the addition.  X points into the buffer, and Y points into the operand string.  To start with, both X and Y point just past the end; therefore the loop BEGINS with a test-and-decrement sequence.  I first t-a-d the buffer pointer; if it is zero, all is finished.  If not, on to t-a-d the string pointer.  If it is zero, there are still digits left in the buffer, so I use an assumed leading zero digit for the operand.  We still may have carries to propagate across the rest of the sum.

Assuming neither pointer is zero, line 2900 gets the next digit from the operand string.  If it is a decimal point, I just store the decimal point ASCII value into the buffer.  If you want to be able to ignore leading blanks, insert the following two lines between line 2920 and 2930:

!lm+5
2924       CMP #'          BLANK?
2925       BEQ .3          YES, USE ZERO.
!lm-5

I left them out in my version, because I forgot I promised it to you.

If the character is not a decimal point (or blank), it may be a minus sign or digit.  I did not put any error checking in my program for other extraneous characters; if you try them, you will get extraneous results!  I treat a sign as a leading zero in the arithmetic loop.

If the character is a digit, or an assumed leading zero, we can add it to the buffer's value.  Lines 2960-3010 will complement the digit if the operand had a minus sign.  Lines 3020-3070 add the current operand digit (or its complement) to the current buffer digit, plus any carry hung over from the preceding digit, and save the resulting carry in CARRY.

That's it!  Now here is a short little Applesoft program to test the code.

!lm+5
100  REM TEST&+$,A$,B$
110  HIMEM: 36864: PRINT  CHR$ (4)"BLOAD B.STRING ADDER":
     CALL 36864
120  INPUT A$: INPUT B$
130  &  + $,A$,B$,C$
140  PRINT C$: GOTO 120
!lm-5
