----TOADADD.DOC----
TOADADD.PAS

Turbo Pascal Inline routines for MS-DOS systems.
First release permits you to add numeric strings to numeric strings,
or integers to numeric strings.

Sounds simple, but this gives you a handle on manipulating literally
infinitely large numbers.  (When the number depicts more than the
number of objects in the universe, that's a pretty good definition
of infinite, ne?)

Code could be rewritten to permit use with Z80 systems, but I'll leave
that as an exercise to the student.  (Don't you just HATE that?)

More to follow ... subtraction, multiplication, division, etc.
(Keep your eyes peeled for TOADMATH.PAS.)  (Unless some other Netlandian
beats me to it.)

Assembler ideas obtained from Chapter 11, "Assembler for the IBM PC and
PC-XT", by Peter Abel ((C) 1984 Reston Publishing Company, Inc.)
for the usual ripoff price.

Works just fine on an 80286 PC clone running PC-DOS 3.1 and Turbo 3.0
.. should be fine on any MS-DOS system and any Turbo version.

Released to the public domain.  (Yep, it's my code.)

David Kirschbaum
Toad Hall
ABN.ISCAMS@USC-ISID.ARPA

----TOADADD.PAS----
program TOADADD.PAS;

{David Kirschbaum
 Toad Hall
 ABN.ISCAMS@USC-ISID.ARPA
 7573 Jennings Lane
 Fayetteville NC  28303
 (919) 868-3471

 Released to the public domain.  For MS-DOS systems and Turbo Pascal.
}
{Methods to provide integer addition for huge numbers ..

Presently limited by Turbo Pascal's limitation of string
variables to 255 bytes.

You COULD use an array for this, extending the length of
your number string to available memory!

 String must be in Data Segment, and should be of the
 format '0000001' or '003270' or whatever.

 YES, it MUST be zero-filled, and NO commas or decimals!
 (We're talking multi-character integers here, remember.)

}

TYPE
  Str20 = STRING[20];
var
  NrStr,
  IntStr : Str20;
  i,code : INTEGER;


PROCEDURE Addit;
  {adds two integer strings together.}
  BEGIN
    Inline(
   $1E              {     push DS}
  /$07              {     pop  ES           ;ES=DS}
  /$31/$DB          {     xor  BX,BX        ;clear msbs}
  /$89/$D9          {     mov  CX,BX}
  /$8A/$0E/>NrStr   {     mov  CL,[>NrStr]  ;the shorter number}
  /$E3/$2B          {     jcxz Exit         ;no length, forget it}

  /$8A/$1E/>IntStr  {     mov  BL,[>IntStr]   ;string length}
  /$8D/$B7/>IntStr  {     lea  SI,>IntStr[BX] ;first char}
  /$89/$F7          {     mov  DI,SI        ;source=destination}

  /$88/$CB          {     mov  BL,CL        ;NrStr length again}
  /$8D/$9F/>NrStr   {     lea  BX,>NrStr[BX] ;point to last char}
  /$51              {     push CX           ;save IntStr length}
  /$56              {     push SI           ;and offsets}
  /$F8              {     clc}
  /$FD              {     std               ;direction right to left}
  /$B4/$00          {B20: mov  AH,0         ;clear AH}
  /$AC              {     lodsb             ;load IntStr byte}
  /$12/$07          {     adc  AL,[BX]      ;add NrStr val}
  /$37              {     aaa               ;adjust for Ascii}
  /$AA              {     stosb             ;store sum in IntStr}
  /$4B              {     dec  BX           ;back up NrStr}
  /$E2/$F6          {     loop B20}
  /$88/$25          {     mov  [DI],AH      ;at end, store carry}
  /$5E              {     pop  SI           ;back to the end again}
  /$89/$F7          {     mov  DI,SI        ;source=destination}
  /$59              {     pop  CX           ;get back NrStr length}
  /$41              {     inc  CX           ;anticipate the carry}
  /$AC              {B30: lodsb             ;snarf the new val}
  /$0C/$30          {     or  AL,$30        ;ascify it}
  /$AA              {     stosb}
  /$E2/$FA          {     loop B30}
                    {exit:}
);
  END;

{Another method:
Lets you add an integer to ANY global string variable.

Both the integer to be added, and the address of the
string variable are brought in as parameters.
}

PROCEDURE Add(nr : INTEGER; VAR S : Str20);
  {could also be:
   PROCEDURE Add(nr : INTEGER; VAR S);
   using Turbo's untyped parameter capability.

   use a string variable S brought in as a parameter,
   and the integer nr, also brought in as a parameter.
  }
  BEGIN
    Inline(
   $8B/$86/>NR      {   mov     AX,>nr[BP]  ;nr of spins}
  /$09/$C0          {   or      AX,AX     ;anything there?}
  /$74/$27          {   je      X1        ; nope, quit this mess}
  /$8B/$9E/>S       {   mov     BX,>S[BP] ;snarf the string address.}
                    {                     ;(LDS or LES would also work,}
                    {                     ;but we're assuming S is a global}
                    {                     ;variable .. makes it much simpler.)}
  /$89/$DF          {   mov     DI,BX     ;offset to S}
  /$43              {   inc     BX        ;bump past length byte}
  /$31/$C9          {   xor     CX,CX     ;clear msb}
  /$8A/$0D          {   mov     CL,[DI]   ;get our integer string length}
  /$01/$CF          {   add     DI,CX     ;point to the last char in the string}
{;}
  /$BE/$0A/$00      {   mov     SI,10     ; put divisor in SI}
  /$31/$D2          {L1:xor     DX,DX     ; clear dividend high word}
  /$80/$2D/$30      {   sub     byte ptr [DI],'0'  ;deasciize the char}
  /$02/$05          {   add     AL,[DI]   ;add our integer msb to the char val}
  /$F7/$F6          {   div     SI        ; AX = (DX:AX)/SI, DX = remainder}
  /$80/$C2/$30      {   add     DL,'0'    ; convert DL remainder byte to ascii}
  /$88/$15          {   mov     byte ptr [DI],DL ; put back in string as char}
  /$4F              {   dec     DI        ; back step in string}
  /$39/$DF          {   cmp     DI,BX     ;hit start?}
  /$76/$04          {   jbe     X1        ; yep, done}
  /$09/$C0          {   or      AX,AX     ; all done? (AX = 0?)}
  /$75/$E9          {   jne     L1        ; if not, do another digit}
);                  {X1:}
  END;  {of Add}

{A third method, assuming you'll only have ONE global
 string variable you'll ALWAYS use for this adding.
 (In this demo, that's IntStr.)
 Saves a little time by not having to pass another parm.
}

PROCEDURE Add_Int(nr : INTEGER);
  { use a global string variable (in this case, IntStr),
   and the integer nr, brought in as a parameter.
  }
  BEGIN
    Inline(
   $8B/$86/>NR      {   mov     AX,>nr[BP]  ;nr of spins}
  /$09/$C0          {   or      AX,AX     ;anything there?}
  /$74/$26          {   je      X2        ; nope, quit this mess}
  /$BB/>IntStr      {   mov     BX,>IntStr ;snarf the string address.}
                    {;or could be}
                    {;  lea     BX,>IntStr}
                    {;(LDS or LES would also work, but we're assuming}
                    {; IntStr is a global variable .. makes it much}
                    {; simpler, and we don't chance messing up DS.)}
  /$89/$DF          {   mov     DI,BX     ;offset to IntStr}
  /$43              {   inc     BX        ;bump past length byte}
  /$31/$C9          {   xor     CX,CX     ;clear msb}
  /$8A/$0D          {   mov     CL,[DI]   ;get our integer string length}
  /$01/$CF          {   add     DI,CX     ;point to the last char in the string}
{;}
  /$BE/$0A/$00      {   mov     SI,10     ; put divisor in SI}
  /$31/$D2          {L2:xor     DX,DX     ; clear dividend high word}
  /$80/$2D/$30      {   sub     byte ptr [DI],'0'  ;deasciize the char}
  /$02/$05          {   add     AL,[DI]   ;add our integer msb to the char val}
  /$F7/$F6          {   div     SI        ; AX = (DX:AX)/SI, DX = remainder}
  /$80/$C2/$30      {   add     DL,'0'    ; convert DL remainder byte to ascii}
  /$88/$15          {   mov     byte ptr [DI],DL ; put back in string as char}
  /$4F              {   dec     DI        ; back step in string}
  /$39/$DF          {   cmp     DI,BX     ;hit start?}
  /$76/$04          {   jbe     X2        ; yep, done}
  /$09/$C0          {   or      AX,AX     ; all done? (AX = 0?)}
  /$75/$E9          {   jne     L2        ; if not, do another digit}
);                  {X2:}
  END;  {of Add_Int}


PROCEDURE Continue;
  VAR ch : CHAR;
  BEGIN
    Write('Press any key to continue: ');
    Repeat until keypressed; read(kbd,ch);
    Writeln;
  END;

begin
  NrStr := '004';
  IntStr := '00000000000000000000';
  Writeln('First add one number string [',NrStr,']');
  Writeln('to a second number string [',IntStr,']');
  Write(IntStr, ' + ', NrStr, ' = ');
  Addit;
  Writeln(IntStr);
  Continue;

  Writeln('Now, add an integer to a number string:');
  FOR i := 1 TO 10 DO BEGIN
    Write(IntStr, ' + ', i:2, ' = ');
    Add(i,IntStr);
    Writeln(IntStr);
  END;
  Continue;

  Writeln('Same process, but using the number string as a global:');
  FOR i := 1 TO 10 DO BEGIN
    Write(IntStr, ' + ', i:2, ' = ');
    Add_Int(i);
    Writeln(IntStr);
  END;
  Continue;

  Writeln('Flashy, user-friendly interactive demo.');
  Writeln('Enter a positive number string (up to 19 chars).');
  Writeln('(Enter a null line (CR) to quit): ');
  Repeat
    Readln(NrStr);
    IF length(NrStr) > 19 THEN NrStr[0] := #19;  {truncate}
    IF NrStr <> '' THEN BEGIN
      Write(IntStr, ' + ', NrStr, ' = ');
      Addit;
      Writeln(IntStr);
    END;
  Until NrStr = '';
end.
