'===========================================================================
' Subject: STRING NUMBER CALC LIBRARY         Date: 04-10-00 (11:22)       
'  Author: Andreas van Cranenburgh            Code: QB, QBasic, PDS        
'  Origin: andreaswolf@mail.com             Packet: MISC.ABC
'===========================================================================
'String-Number library                    By Andreas van Cranenburgh (C) 2000
'    Mail any suggestions and bug reports to andreaswolf@mail.com
'You can calculate with numbers of upto 32767 digits (limit of a string).
'The code is sometimes very inefficient and buggy so don't expect too much.
'Things that seem to work:
' o Addition
' o Substraction
' o (integer) Powers
' o Multiplication
' o Faculty
'Things that need work:
' o (integer) Division (it works correct but it is VERY slow)
' o Modulus (same)
'Things that I do not know how to make:
' o Square root  (yes, I can think of something very inefficient)
'Things that will NOT be implented:
' o Floating point
'Zero is returned as "". Error checking is done but it is not possible to check
'what error occured.
DEFINT A-Z                              'All variables are assumed to be integers.
DECLARE SUB NumTrim (Num$)              'Delete spaces and excess of zeros in a number.
DECLARE FUNCTION StrAdd$ (Num1$, Num2$) 'Add two numbers.
DECLARE FUNCTION StrSub$ (Num1$, Num2$) 'Subtract two numbers.
DECLARE FUNCTION StrMul$ (Num1$, Num2$) 'Multiplicate two numbers.
DECLARE FUNCTION StrDiv$ (Num1$, Num2$) 'Divide two numbers
DECLARE FUNCTION StrPwr$ (Num1$, Num2$) 'Raise Num1$ to the power of Num2$
DECLARE FUNCTION StrMod$ (Num1$, Num2$) 'Modulus of Num1$ and Num2$
DECLARE FUNCTION StrFac$ (Fac$)         'Returns faculty of a number
DECLARE FUNCTION StrSmaller (Num1$, Num2$) 'Is first one smaller than last one?
DECLARE FUNCTION StrGreater (Num1$, Num2$) 'Is first one greater than last one?
DECLARE FUNCTION Faculty# (Num)         'Returns faculty (a maximum of 171)
DECLARE FUNCTION Min (Num1, Num2)       'Returns lowest number of two numbers.
DECLARE FUNCTION Max (Num1, Num2)       'Returns highest number of two numbers.
'-Í=ð=Í-Í=ð=Í-Í=ð=Í-DEMO STARTS HEREð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-ÍON ERROR GOTO Handler                   'To trap division by zero
CLS                                     'Clear screen
INPUT "Enter first number: ", Num1$
INPUT "Enter second number: ", Num2$
NumTrim Num1$
NumTrim Num2$
OldNum1$ = Num1$
OldNum2$ = Num2$
PRINT "               ", "StrNum:", "QuickBasic:"
PRINT "Sum:           ", StrAdd$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) + VAL(Num2$)))
PRINT "Difference:    ", StrSub$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) - VAL(Num2$)))
PRINT "Product:       ", StrMul$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) * VAL(Num2$)))
PRINT "Quotient (INT):", StrDiv$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) \ VAL(Num2$)))
PRINT "Modulus:       ", StrMod$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) MOD VAL(Num2$)))
PRINT "Power:         ", StrPwr$(Num1$, Num2$), LTRIM$(STR$(VAL(Num1$) ^ VAL(Num2$)))
PRINT "Calculate faculty? [Y,N] ";
DO                                  'Poll keyboard until right answer is given
  k$ = INKEY$
LOOP UNTIL UCASE$(k$) = "Y" OR UCASE$(k$) = "N"
PRINT k$;
LOCATE , 1
PRINT "Faculty (of first number):",
t! = TIMER
a$ = StrFac$(Num1$)
t! = TIMER - t!         'Calculate time needed to calculate faculty
PRINT a$,
IF VAL(Num1$) < 171 THEN          '171 is the maximum, any higher will cause overflow
  PRINT LTRIM$(STR$(Faculty#(VAL(Num1$))))
END IF
PRINT "Calculated in "; t!; "seconds"
FOR a = LEN(a$) TO 1 STEP -1
  IF MID$(a$, a, 1) <> "0" THEN EXIT FOR
NEXT a
PRINT LEN(a$) - a; "zero's in faculty"
PRINT "Write to file? [Y,N] ";
DO                                  'Poll keyboard until right answer is given
  k$ = INKEY$
LOOP UNTIL UCASE$(k$) = "Y" OR UCASE$(k$) = "N"
PRINT k$
IF UCASE$(k$) = "Y" THEN
  INPUT "Enter file: "; File$
  OPEN File$ FOR OUTPUT AS #1        'First write some shameless selfpromotion...
  PRINT #1, "The factorial of "; Num1$; " calulated by Andreas van Cranenburgh's StrNum"
  PRINT #1,
  PRINT #1, a$
  CLOSE #1
END IF
END
Handler:                'Error handler to make sure program doesn't end after
RESUME NEXT             'a division by zero
'-Í=ð=Í-Í=ð=Í-Í=ð=Í-DEMO ENDS HERE-=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í=ð=Í-Í
FUNCTION Faculty# (Num)
'Calculate faculty without recursion
IF Num = 0 OR Num = 1 THEN
  Faculty# = 1
  EXIT FUNCTION
END IF
b# = 1
FOR a = 2 TO Num
  b# = b# * a
NEXT a
Faculty# = b#
END FUNCTION

FUNCTION Max (Num1, Num2)
IF Num1 > Num2 THEN Max = Num1 ELSE Max = Num2
END FUNCTION

FUNCTION Min (Num1, Num2)
IF Num1 < Num2 THEN Min = Num1 ELSE Min = Num2
END FUNCTION


SUB NumTrim (Num$)
'Delete excess of spaces and zero's from string number
FOR a = 1 TO LEN(Num$)
  IF MID$(Num$, a, 1) <> "0" AND MID$(Num$, a, 1) <> CHR$(0) THEN EXIT FOR
NEXT a
Num$ = MID$(Num$, a)
FOR a = LEN(Num$) TO 1 STEP -1
  IF MID$(Num$, a, 1) <> CHR$(0) THEN EXIT FOR
NEXT a
Num$ = LEFT$(Num$, a)
END SUB

FUNCTION StrAdd$ (Num1$, Num2$)
DIM Keep                'Holds values to be transported to next digit
DIM OutNum$             'Holds the result
DIM Swapped             'True if variables were swapped (need to be swapped again)
NumTrim Num1$
NumTrim Num2$
IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN      ' -a + b = b -a
  StrAdd$ = StrSub$(Num2$, MID$(Num1$, 2))
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) <> "-" AND LEFT$(Num2$, 1) = "-" THEN      ' a + -b = b - a
  StrAdd$ = "-" + StrSub$(MID$(Num2$, 2), Num1$)
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) = "-" THEN       ' -a + -b = -(a+b)
  Num1$ = MID$(Num1$, 2)
  Num2$ = MID$(Num2$, 2)
  SignDel = -1
  Sign$ = "-"
END IF

IF LEN(Num1$) < LEN(Num2$) THEN
  OutNum$ = STRING$(LEN(Num2$) - LEN(Num1$), "0") + Num2$
  Num1$ = STRING$(2 * (LEN(Num2$) - LEN(Num1$)), 48) + Num1$
ELSE
  Swapped = -1
  SWAP Num1$, Num2$
  Num1$ = STRING$(LEN(Num2$) - LEN(Num1$) + 1, 48) + Num1$
  OutNum$ = "0" + Num2$
END IF
FOR a = LEN(Num1$) TO 1 STEP -1
  OutCome = (ASC(MID$(Num1$, a, 1)) - 48) + (ASC(MID$(OutNum$, a, 1)) - 48) + Keep
  IF OutCome > 9 THEN
    Keep = OutCome \ 10             'the excess
    OutCome = OutCome MOD 10        'the rest
  ELSE
    Keep = 0
  END IF
  IF OutCome >= 0 AND OutCome <= 9 THEN MID$(OutNum$, a, 1) = CHR$(OutCome + 48)
NEXT a
IF Swapped THEN SWAP Num1$, Num2$
IF SignDel THEN
  Num1$ = "-" + Num1$
  Num2$ = "-" + Num2$
END IF
NumTrim OutNum$
StrAdd$ = Sign$ + OutNum$
END FUNCTION

FUNCTION StrDiv$ (Num1$, Num2$)
'Perform integer division
NumTrim Num1$
NumTrim Num2$
IF Num2$ = "" THEN EXIT FUNCTION             'Division by zero....

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN    ' -a * b = -(a * b)
  Sign$ = "-"
  Num1$ = MID$(Num1$, 2)
  SignDel = 1
END IF

IF LEFT$(Num2$, 1) = "-" AND LEFT$(Num1$, 1) <> "-" THEN    ' a * -b = -(a * b)
  Sign$ = "-"
  Num2$ = MID$(Num2$, 2)
  SignDel = 2
END IF

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) = "-" THEN    ' -a * -b = a * b
  Num1$ = MID$(Num1$, 2)
  Num2$ = MID$(Num2$, 2)
  SignDel = 3
END IF

IF Num2$ = "1" THEN                          'Division by one
  StrDiv$ = Num1$
  EXIT FUNCTION
END IF
IF Num2$ = Num1$ THEN                        'Division by itself
  StrDiv$ = "1"
  EXIT FUNCTION
END IF
IF StrSmaller(Num1$, Num2$) THEN             'This is an integer division!
  StrDiv$ = "0"
  EXIT FUNCTION
END IF
OutNum$ = "1" + STRING$(LEN(Num1$) - LEN(Num2$), "0")
OutTest$ = StrMul$(Num2$, OutNum$)
DO
  OutTest$ = StrAdd$(OutTest$, Num2$)
  IF StrGreater(OutTest$, Num1$) THEN
    StrDiv$ = Sign$ + OutNum$
    SELECT CASE SignDel
    CASE 1
      Num1$ = "-" + Num1$
    CASE 2
      Num2$ = "-" + Num2$
    CASE 3
      Num1$ = "-" + Num1$
      Num2$ = "-" + Num2$
    END SELECT
    EXIT FUNCTION
  END IF
  OutNum$ = StrAdd$(OutNum$, "1")
  LOCATE , 30
  PRINT OutNum$;
LOOP
'Program shouldn't reach this point.
END FUNCTION

FUNCTION StrFac$ (Fac$)
'Returns faculty of Fac
NumTrim Fac$
IF StrSmaller(Fac$, "0") THEN EXIT FUNCTION
IF Fac$ = "" OR Fac$ = "1" THEN
  StrFac$ = "1"
  EXIT FUNCTION
END IF
OutCome$ = "1"
p = POS(0)
Counter$ = "2"
IF LEN(Fac$) < 17 THEN
  Fac# = VAL(Fac$)                   'This method is WAY faster...
  FOR a# = 2 TO Fac#
    OutCome$ = StrMul$(OutCome$, LTRIM$(STR$(a#)))
    LOCATE , p                      '***remove***
    COLOR 23, 0: PRINT "Faculty: "; '***remove***
    COLOR 15, 0: PRINT a#;    '***remove***
  NEXT a#
ELSE
  DO
    OutCome$ = StrMul$(OutCome$, Counter$)
    LOCATE , p                      '***remove***
    COLOR 23, 0: PRINT "Faculty: "; '***remove***
    COLOR 15, 0: PRINT Counter$;    '***remove***
    Counter$ = StrAdd$(Counter$, "1")
  LOOP UNTIL Counter$ = StrAdd$(Fac$, "1")
END IF
LOCATE , p                        '***remove***
PRINT "               ";          '***remove***
LOCATE , p                        '***remove***
NumTrim OutCome$
StrFac$ = OutCome$
END FUNCTION

FUNCTION StrGreater (Num1$, Num2$)
'Returns true if Num1$ is greater than Num2$
NumTrim Num1$
NumTrim Num2$
IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN      ' -a + b = b -a
  StrGreater = 0
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) <> "-" AND LEFT$(Num2$, 1) = "-" THEN      ' a + -b = b - a
  StrGreater = -1
  EXIT FUNCTION
END IF

IF Num1$ = Num2$ THEN
  EXIT FUNCTION                             'Useless to test
ELSEIF LEN(Num1$) > LEN(Num2$) THEN         'Also
  StrGreater = -1
  EXIT FUNCTION
ELSEIF LEN(Num1$) < LEN(Num2$) THEN         'Also
  StrGreater = 0
  EXIT FUNCTION
ELSEIF LEN(Num1$) = LEN(Num2$) THEN         'Here begins the real work
  FOR a = 1 TO LEN(Num1$)
    IF ASC(MID$(Num1$, a, 1)) > ASC(MID$(Num2$, a, 1)) THEN
      StrGreater = -1
      EXIT FUNCTION
    ELSEIF ASC(MID$(Num1$, a, 1)) < ASC(MID$(Num2$, a, 1)) THEN
      StrGreater = 0
      EXIT FUNCTION
    END IF
  NEXT a
END IF
'Program shouldn't reach this point.
END FUNCTION


FUNCTION StrMod$ (Num1$, Num2$)
'Modulus (remainder of an integer division)
NumTrim Num1$
NumTrim Num2$
IF Num2$ = "" THEN EXIT FUNCTION             'Division by zero....
IF Num2$ = "1" THEN                          'Division by one
  StrMod$ = "0"
  EXIT FUNCTION
END IF
IF Num2$ = Num1$ THEN                        'Division by itself
  StrMod$ = "0"
  EXIT FUNCTION
END IF
IF StrSmaller(Num1$, Num2$) THEN             'Faster
  StrMod$ = Num1$
  EXIT FUNCTION
END IF
'VERY inefficient but I needed something that worked....
StrMod$ = StrSub$(Num1$, StrMul$(StrDiv$(Num1$, Num2$), Num2$))
'Program shouldn't reach this point.
END FUNCTION

FUNCTION StrMul$ (Num1$, Num2$)
DIM Keep                'Holds values to be transported to next digit
DIM OutNum$             'Holds the result
DIM OutStep$            'Holds eaqch step of the result
NumTrim Num1$
NumTrim Num2$
IF Num1$ = "" OR Num2$ = "" THEN EXIT FUNCTION          'Zero...

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN    ' -a * b = -(a * b)
  Sign$ = "-"
  Num1$ = MID$(Num1$, 2)
  SignDel = 1
END IF

IF LEFT$(Num2$, 1) = "-" AND LEFT$(Num1$, 1) <> "-" THEN    ' a * -b = -(a * b)
  Sign$ = "-"
  Num2$ = MID$(Num2$, 2)
  SignDel = 2
END IF

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) = "-" THEN    ' -a * -b = a * b
  Num1$ = MID$(Num1$, 2)
  Num2$ = MID$(Num2$, 2)
  SignDel = 3
END IF

FOR a = LEN(Num2$) TO 1 STEP -1
  OutStep$ = STRING$(LEN(Num1$) + LEN(Num2$), "0")
  FOR b = LEN(Num1$) TO 1 STEP -1
    OutCome = (ASC(MID$(Num1$, b, 1)) - 48) * (ASC(MID$(Num2$, a, 1)) - 48)
    OutCome = OutCome + Keep
    IF OutCome > 9 THEN
      Keep = OutCome \ 10             'the excess
      OutCome = OutCome MOD 10        'the rest
    ELSE
      Keep = 0
    END IF
    IF OutCome >= 0 AND OutCome < 10 THEN MID$(OutStep$, b + a, 1) = CHR$(OutCome + 48)
  NEXT b
  IF Keep THEN
    MID$(OutStep$, b + a, 1) = CHR$(Keep + 48)
    Keep = 0
  END IF
  OutNum$ = StrAdd$(OutNum$, OutStep$)
NEXT a
NumTrim OutNum$
SELECT CASE SignDel
CASE 1
  Num1$ = "-" + Num1$
CASE 2
  Num2$ = "-" + Num2$
CASE 3
  Num1$ = "-" + Num1$
  Num2$ = "-" + Num2$
END SELECT
StrMul$ = Sign$ + OutNum$
END FUNCTION

FUNCTION StrPwr$ (Num1$, Num2$)
'Raise Num1$ to the power of Num2$. You can only raise to an integer power
'higher than zero.
TempNum$ = Num2$
OutCome$ = "1"
NumTrim Num1$
NumTrim Num2$
IF LEFT$(Num1$, 1) = "-" THEN
  Sign$ = "-"
END IF
IF LEFT$(Num2$, 1) = "-" THEN EXIT FUNCTION     'Don't know how this works...
IF Num2$ = "" THEN
  StrPwr$ = "1"
  EXIT FUNCTION
ELSEIF Num2$ = "1" THEN
  StrPwr$ = Num1$
  EXIT FUNCTION
END IF

DO
  TempNum$ = StrSub$(TempNum$, "1")
  OutCome$ = StrMul$(OutCome$, Num1$)
LOOP UNTIL TempNum$ = "0"
IF LEFT$(OutCome$, 1) = "-" THEN Sign$ = ""
StrPwr$ = Sign$ + OutCome$
END FUNCTION

FUNCTION StrSmaller (Num1$, Num2$)
'Returns true if Num1$ is smaller than Num2$
NumTrim Num1$
NumTrim Num2$
IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN      ' -a + b = b -a
  StrSmaller = -1
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) <> "-" AND LEFT$(Num2$, 1) = "-" THEN      ' a + -b = b - a
  StrSmaller = 0
  EXIT FUNCTION
END IF

IF Num1$ = Num2$ THEN
  EXIT FUNCTION                             'Useless to test
ELSEIF LEN(Num1$) > LEN(Num2$) THEN         'Also
  StrSmaller = 0
  EXIT FUNCTION
ELSEIF LEN(Num1$) < LEN(Num2$) THEN         'Also
  StrSmaller = -1
  EXIT FUNCTION
ELSEIF LEN(Num1$) = LEN(Num2$) THEN         'Here begins the real work
  FOR a = 1 TO LEN(Num1$)
    IF ASC(MID$(Num1$, a, 1)) < ASC(MID$(Num2$, a, 1)) THEN
      StrSmaller = -1
      EXIT FUNCTION
    ELSEIF ASC(MID$(Num1$, a, 1)) > ASC(MID$(Num2$, a, 1)) THEN
      StrSmaller = 0
      EXIT FUNCTION
    END IF
  NEXT a
END IF
'Program shouldn't reach this point.
END FUNCTION

FUNCTION StrSub$ (Num1$, Num2$)
DIM Keep                'Holds values to be transported to next digit
DIM OutNum$             'Holds the result
NumTrim Num1$
NumTrim Num2$
IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) <> "-" THEN      ' -a - b = -(a + b)
  StrSub$ = "-" + StrAdd$(Num2$, MID$(Num1$, 2))
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) <> "-" AND LEFT$(Num2$, 1) = "-" THEN      ' a - -b = a + -(b)
  StrSub$ = StrAdd$(MID$(Num2$, 2), Num1$)
  EXIT FUNCTION
END IF

IF LEFT$(Num1$, 1) = "-" AND LEFT$(Num2$, 1) = "-" THEN       ' -a - -b = -(b - a)
  SWAP Num1$, Num2$
  Swapped = -1
  Num1$ = MID$(Num1$, 2)
  Num2$ = MID$(Num2$, 2)
  SignDel = -1
END IF

IF Num1$ = "" THEN
  StrSub$ = "-" + Num2$
  EXIT FUNCTION
END IF
IF Num2$ = "" THEN
  StrSub$ = Num1$
  EXIT FUNCTION
END IF
IF Num1$ = Num2$ THEN
  StrSub$ = "0"
  IF SingDel THEN
    Num1$ = "-" + Num1$
    Num2$ = "-" + Num2$
  END IF
  EXIT FUNCTION
END IF
IF StrGreater(Num2$, Num1$) THEN
  Sign$ = "-"
  Swapped = NOT Swapped
  SWAP Num1$, Num2$
END IF
IF LEN(Num1$) < LEN(Num2$) THEN
  Num1$ = STRING$(LEN(Num2$) - LEN(Num1$) + 1, 48) + Num1$
  OutNum$ = "0" + Num2$
ELSEIF LEN(Num1$) > LEN(Num2$) THEN
  OutNum$ = STRING$(LEN(Num1$) - LEN(Num2$) + 1, 48) + Num2$
  Num1$ = "0" + Num1$
ELSE
  OutNum$ = Num2$
END IF
FOR a = LEN(Num1$) TO 1 STEP -1
  OutCome = (ASC(MID$(Num1$, a, 1)) - 48) - (ASC(MID$(OutNum$, a, 1)) - 48) - Keep
  IF OutCome < 0 THEN
    OutCome = 10 - (ASC(MID$(OutNum$, a, 1)) - 48) - Keep   'seems to work...
    Keep = -(OutCome \ 10) + 1                              'the excess
  ELSE
    Keep = 0
  END IF
  IF OutCome >= 0 AND OutCome <= 9 THEN MID$(OutNum$, a, 1) = CHR$(OutCome + 48)
NEXT a
NumTrim OutNum$
IF OutNum$ = "" THEN OutNum$ = "0"
IF Swapped THEN SWAP Num1$, Num2$
IF SignDel THEN
  Num1$ = "-" + Num1$
  Num2$ = "-" + Num2$
END IF
StrSub$ = Sign$ + OutNum$
END FUNCTION
