Author Topic: String Math  (Read 9567 times)

0 Members and 1 Guest are viewing this topic.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #15 on: August 15, 2020, 05:12:46 pm »
Just thought of a great way to test mult$ function. How about first 500 factorials check?

If the 500th is correct then it is pretty good assumption that all that went before it is correct.
1135 digits all good! And done in a sneeze.
Code: QB64: [Select]
  1. _TITLE "String Math Factorial test" 'b+ started 2020-08-15 using to add$() and mult$ for Factorials
  2. ' 2020-08-14 start with add 2 arbitrary long strings
  3. ' 2020-08-14 add$ posted 5 PM or so
  4. ' 2020-08-14 opt explicit, start subtr$ function
  5. ' 2020-08-15 1 AM subst$ function looking good!
  6. ' 2020-08-15 fix some code from previous subs then attempt mult$ sub
  7. ' 2020-08-15 Factorials! a wonderful test for String Math mult$
  8. ' 2020-08-15 WOW! 500 factorials in an instant and it's matching 500!
  9. '            at  https://www.calculatorsoup.com/calculators/discretemathematics/factorials.php
  10.  
  11. RANDOMIZE TIMER 'now that it's seems to be running silent
  12. SCREEN _NEWIMAGE(1024, 700, 32)
  13. _DELAY .25
  14.  
  15. FactorialSetup
  16.  
  17.  
  18. FUNCTION Factorial$ (n AS INTEGER)
  19.     DIM top AS INTEGER, fline$
  20.     'find the highest factorial we have so far
  21.     OPEN "Factorial Data.txt" FOR INPUT AS #1
  22.     WHILE EOF(1) = 0
  23.         LINE INPUT #1, fline$
  24.         top = top + 1
  25.         IF n = top THEN Factorial$ = rightOf$(fline$, "="): EXIT FUNCTION
  26.     WEND
  27.  
  28.     'finish later
  29.  
  30.  
  31. SUB FactorialSetup
  32.     DIM i AS INTEGER, fac$
  33.     IF _FILEEXISTS("Factorial Data.txt") THEN 'assume its started and correct
  34.         EXIT SUB
  35.     ELSE
  36.         OPEN "Factorial Data.txt" FOR OUTPUT AS #1
  37.         fac$ = "1"
  38.         PRINT #1, "1=1"
  39.         FOR i = 2 TO 500
  40.             fac$ = mult$(_TRIM$(STR$(i)), fac$)
  41.             PRINT #1, _TRIM$(STR$(i)) + "=" + fac$
  42.             PRINT i, fac$
  43.         NEXT
  44.         CLOSE #1
  45.     END IF
  46.  
  47. FUNCTION mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  48.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, i AS INTEGER, find AS INTEGER, dp AS INTEGER
  49.     DIM f18$, f1$, t$, build$, accum$
  50.  
  51.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  52.     la = LEN(a$): lb = LEN(b$)
  53.     IF la > lb THEN
  54.         m = INT(la / 18) + 1
  55.         f18$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  56.         f1$ = b$
  57.     ELSE
  58.         m = INT(lb / 18) + 1
  59.         f18$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  60.         f1$ = a$
  61.     END IF
  62.     FOR dp = LEN(f1$) TO 1 STEP -1 'dp = digit position of the f1$
  63.         build$ = "" 'line builder
  64.         co = 0
  65.         'now taking 18 digits at a time Thanks Steve McNeill
  66.         FOR g = 1 TO m
  67.             v18 = VAL(MID$(f18$, m * 18 - g * 18 + 1, 18))
  68.             sd = VAL(MID$(f1$, dp, 1))
  69.             t$ = RIGHT$(STRING$(19, "0") + _TRIM$(STR$(v18 * sd + co)), 19)
  70.             co = VAL(MID$(t$, 1, 1))
  71.             build$ = MID$(t$, 2) + build$
  72.         NEXT g
  73.         IF co THEN build$ = _TRIM$(STR$(co)) + build$
  74.         IF dp = LEN(f1$) THEN
  75.             accum$ = build$
  76.         ELSE
  77.             accum$ = add$(accum$, build$ + STRING$(LEN(f1$) - dp, "0"))
  78.         END IF
  79.     NEXT dp
  80.     'strip 0
  81.     i = 1: find = 0
  82.     WHILE i < LEN(accum$) AND MID$(accum$, i, 1) = "0"
  83.         i = i + 1: find = 1
  84.     WEND
  85.     IF find = 1 THEN accum$ = MID$(accum$, i)
  86.     mult$ = accum$
  87.  
  88. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  89.     IF sum$ = minus$ THEN subtr$ = "0": EXIT SUB
  90.  
  91.     DIM ls AS INTEGER, lm AS INTEGER, m AS INTEGER, g AS INTEGER, i AS INTEGER, find AS INTEGER, p AS INTEGER
  92.     DIM sign$, LG$, sm$, t$, result$
  93.  
  94.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  95.     ' which is bigger?
  96.     ls = LEN(sum$): lm = LEN(minus$)
  97.     IF ls > lm THEN
  98.         sign$ = ""
  99.         m = INT(ls / 18) + 1
  100.         LG$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  101.         sm$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  102.     ELSEIF ls < lm THEN
  103.         sign$ = "-"
  104.         m = INT(lm / 18) + 1
  105.         LG$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  106.         sm$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  107.     ELSEIF ls = lm THEN
  108.         FOR i = 1 TO LEN(sum$)
  109.             IF VAL(MID$(sum$, i, 1)) > VAL(MID$(minus$, i, 1)) THEN
  110.                 sign$ = ""
  111.                 m = INT(ls / 8) + 1
  112.                 LG$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  113.                 sm$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  114.                 EXIT FOR
  115.             ELSEIF VAL(MID$(sum$, i, 1)) < VAL(MID$(minus$, i, 1)) THEN
  116.                 sign$ = "-"
  117.                 m = INT(lm / 18) + 1
  118.                 LG$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  119.                 sm$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  120.                 EXIT FOR
  121.             END IF
  122.         NEXT
  123.     END IF
  124.     'now taking 18 digits at a time From Steve I learned we can do 18
  125.     FOR g = 1 TO m
  126.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  127.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  128.         IF vs > VB THEN
  129.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  130.  
  131.             'debug
  132.             'PRINT VB, tenE18, tenE18 - vs + VB, " t$ = "; t$
  133.  
  134.             ''borrow 1 = rewrite string
  135.             p = (m - g) * 18
  136.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  137.                 MID$(LG$, p, 1) = "9"
  138.                 p = p - 1
  139.             WEND
  140.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  141.         ELSE
  142.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  143.         END IF
  144.         result$ = t$ + result$
  145.     NEXT
  146.     result$ = _TRIM$(result$)
  147.     'strip 0
  148.     i = 1: find = 0
  149.     WHILE i < LEN(result$) AND MID$(result$, i, 1) = "0"
  150.         i = i + 1: find = 1
  151.     WEND
  152.     IF find = 1 THEN result$ = MID$(result$, i)
  153.     subtr$ = sign$ + result$
  154.  
  155. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  156.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, i AS INTEGER, find AS INTEGER
  157.     DIM fa$, fb$, t$, new$, result$
  158.  
  159.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  160.     la = LEN(a$): lb = LEN(b$)
  161.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  162.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  163.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  164.  
  165.     'now taking 18 digits at a time Thanks Steve McNeill
  166.     FOR g = 1 TO m
  167.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  168.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  169.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  170.         co = VAL(MID$(t$, 1, 18))
  171.         new$ = MID$(t$, 19)
  172.         result$ = new$ + result$
  173.  
  174.         'debug
  175.         'DIM w$
  176.         'PRINT a$, m * 18, sa, sb, t$, co, result$
  177.         'INPUT "OK "; w$ 'OK
  178.     NEXT
  179.     IF co THEN result$ = STR$(co) + result$
  180.     result$ = _TRIM$(result$)
  181.     'strip 0
  182.     i = 1: find = 0
  183.     WHILE i < LEN(result$) AND MID$(result$, i, 1) = "0"
  184.         i = i + 1: find = 1
  185.     WEND
  186.     IF find = 1 THEN result$ = MID$(result$, i)
  187.     add$ = result$
  188.  
  189. FUNCTION leftOf$ (source$, of$)
  190.     IF INSTR(source$, of$) > 0 THEN leftOf$ = MID$(source$, 1, INSTR(source$, of$) - 1)
  191.  
  192. FUNCTION rightOf$ (source$, of$)
  193.     IF INSTR(source$, of$) > 0 THEN rightOf$ = MID$(source$, INSTR(source$, of$) + LEN(of$))
  194.  
  195.  
  196.  

 
Check Factorial 500!.PNG

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #16 on: August 16, 2020, 02:48:15 pm »
Looks like I should change the code for subtr$() function.
Update, failed:
Code: QB64: [Select]
  1. PRINT "9 minus 3 but 3 is tricky! "; subtr$("9", "00003")
  2.  

Fix one thing and another goes bad:
Code: QB64: [Select]
  1. _TITLE "String Math subtr$" 'b+ started 2020-08-14
  2. ' 2020-08-14 start with add 2 arbitrary long strings
  3. ' 2020-08-14 add$ posted 5 PM or so
  4. ' 2020-08-14 opt explicit, start subtr$ function
  5. ' 2020-08-15 1 AM subst$ function looking good!
  6. ' 2020-08-16 problem found with string compare and QB64 fails compare with longer strings
  7.  
  8. 'RANDOMIZE TIMER 'now that it's seems to be running silent
  9. SCREEN _NEWIMAGE(1024, 700, 32)
  10. _DELAY .25
  11.  
  12. ' special or hard cases found while testing
  13. PRINT subtr$("0", "0")
  14. PRINT subtr$("11111111111111111375", "11111111111111111374")
  15. PRINT subtr$("11111111111111111374", "11111111111111111375")
  16. PRINT "9 minus 3 but 3 is tricky! "; subtr$("9", "00003")
  17. PRINT subtr$("1" + STRING$(50, "0"), "1"), LEN(subtr$("1" + STRING$(50, "0"), "1"))
  18. PRINT subtr$("980", "5")
  19. PRINT " Answer supposed to be 1: "; subtr$("1" + STRING$(99, "0"), STRING$(99, "9"))
  20. PRINT "Answer supposed to be -1: "; subtr$(STRING$(99, "9"), "1" + STRING$(99, "0"))
  21. PRINT "press any for Random testing against QB64 math calculations..."
  22.  
  23. 'random testing
  24. DIM al AS LONG, bl AS LONG, sum AS _INTEGER64, e1 AS INTEGER, e2 AS INTEGER, a$, b$, subtrStr$, qbCalc$
  25.     'pick two numbers
  26.     e1 = INT(10 * RND): e2 = INT(10 * RND)
  27.     al = RND * 10 ^ e1: bl = RND * 10 ^ e2: sum = al - bl
  28.     a$ = _TRIM$(STR$(al)): b$ = _TRIM$(STR$(bl))
  29.     PRINT
  30.     PRINT a$; " minus "; b$; " ="
  31.     subtrStr$ = subtr$(a$, b$)
  32.     PRINT subtrStr$; " according to subtr$ function we are testing."
  33.     qbCalc$ = _TRIM$(STR$(sum))
  34.     PRINT qbCalc$; " according to QB64 math"
  35.     IF qbCalc$ <> subtrStr$ THEN BEEP: SLEEP ' <<<<<<<<<< stop when find an discrepency
  36.     PRINT "Next sum check in 10 secs, or press key,  press escape to quit..."
  37.     SLEEP 10 '
  38.  
  39. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  40.     IF sum$ = minus$ THEN subtr$ = "0": EXIT SUB
  41.  
  42.     DIM ls AS INTEGER, lm AS INTEGER, m AS INTEGER, g AS INTEGER, i AS INTEGER, find AS INTEGER, p AS INTEGER
  43.     DIM sign$, LG$, sm$, t$, new$, result$
  44.  
  45.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  46.     ' which is bigger?
  47.     ls = LEN(sum$): lm = LEN(minus$)
  48.     IF sum$ > minus$ THEN ' QB64 seems to do great comaparing number strings as if unsigned integers
  49.         sign$ = ""
  50.         m = INT(ls / 18) + 1
  51.         LG$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  52.         sm$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  53.     ELSE
  54.         sign$ = "-"
  55.         m = INT(lm / 18) + 1
  56.         LG$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  57.         sm$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  58.     END IF
  59.  
  60.  
  61.     'IF ls > lm THEN
  62.     '    sign$ = ""
  63.     '    m = INT(ls / 18) + 1
  64.     '    LG$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  65.     '    sm$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  66.     'ELSEIF ls < lm THEN
  67.     '    sign$ = "-"
  68.     '    m = INT(lm / 18) + 1
  69.     '    LG$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  70.     '    sm$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  71.     'ELSEIF ls = lm THEN
  72.     '    FOR i = 1 TO LEN(sum$)
  73.     '        IF VAL(MID$(sum$, i, 1)) > VAL(MID$(minus$, i, 1)) THEN
  74.     '            sign$ = ""
  75.     '            m = INT(ls / 8) + 1
  76.     '            LG$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  77.     '            sm$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  78.     '            EXIT FOR
  79.     '        ELSEIF VAL(MID$(sum$, i, 1)) < VAL(MID$(minus$, i, 1)) THEN
  80.     '            sign$ = "-"
  81.     '            m = INT(lm / 18) + 1
  82.     '            LG$ = RIGHT$(STRING$(m * 18, "0") + minus$, m * 18)
  83.     '            sm$ = RIGHT$(STRING$(m * 18, "0") + sum$, m * 18)
  84.     '            EXIT FOR
  85.     '        END IF
  86.     '    NEXT
  87.     'END IF
  88.  
  89.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  90.     FOR g = 1 TO m
  91.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  92.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  93.         IF vs > VB THEN
  94.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  95.  
  96.             'debug
  97.             'PRINT VB, tenE18, tenE18 - vs + VB, " t$ = "; t$
  98.  
  99.             ''borrow 1 = rewrite string
  100.             p = (m - g) * 18
  101.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  102.                 MID$(LG$, p, 1) = "9"
  103.                 p = p - 1
  104.             WEND
  105.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  106.         ELSE
  107.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  108.         END IF
  109.         result$ = t$ + result$
  110.     NEXT
  111.     result$ = _TRIM$(result$)
  112.     'strip 0
  113.     i = 1: find = 0
  114.     WHILE i < LEN(result$) AND MID$(result$, i, 1) = "0"
  115.         i = i + 1: find = 1
  116.     WEND
  117.     IF find = 1 THEN result$ = MID$(result$, i)
  118.     subtr$ = sign$ + result$
  119.  
  120. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  121.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  122.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, i AS INTEGER, find AS INTEGER
  123.     DIM fa$, fb$, t$, new$, result$
  124.     la = LEN(a$): lb = LEN(b$)
  125.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  126.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  127.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  128.  
  129.     'now taking 18 digits at a time Thanks Steve McNeill
  130.     FOR g = 1 TO m
  131.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  132.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  133.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  134.         co = VAL(MID$(t$, 1, 18))
  135.         new$ = MID$(t$, 19)
  136.         result$ = new$ + result$
  137.  
  138.         'debug
  139.         'DIM w$
  140.         'PRINT a$, m * 18, sa, sb, t$, co, result$
  141.         'INPUT "OK "; w$ 'OK
  142.     NEXT
  143.     IF co THEN result$ = STR$(co) + result$
  144.     result$ = _TRIM$(result$)
  145.     'strip 0
  146.     i = 1: find = 0
  147.     WHILE i < LEN(result$) AND MID$(result$, i, 1) = "0"
  148.         i = i + 1: find = 1
  149.     WEND
  150.     IF find = 1 THEN result$ = MID$(result$, i)
  151.     add$ = result$
  152.  

OK guess I have to roll my own.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • Steve’s QB64 Archive Forum
Re: String Math
« Reply #17 on: August 16, 2020, 04:11:00 pm »
You need a sign checker routine in your subs.   (Cavet: I've been taking care of mama for the last several days and haven't looked at your code closely yet, so you may have the following already:)

First, a function to deal with doubles.  FUNCTION DWD (text$) searches text$ and converts double symbols to reverse symbols.  ++ becomes +, -- becomes +, -+ becomes -, +- becomes -.   

For example, 3 + -2 (Three plus negative two) becomes 3 - 2.

Then in your routines, check to make certain that your two values have the same sign.

-3 -4 -- this is addition, as the two values are both negative.  Just kick it from your subtraction routine to your addition routine and return the result from there.
-3 + 4 -- this is subtraction, even though the symbol between them in +.  Once again, just kick it to the appropriate function and return the result from there.

And a routine to convert scientific notion to string is nice as well, so you can add 3E+20 to 1234567899012345678987654321.

If you look in QB64.BAS, you'll find most of these little helper routines already worked up and in use inside there, as they're part of the CONST calculations we do.  (Also in my math evaluator, which works with strings as well.)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #18 on: August 16, 2020, 04:28:09 pm »
Hi Steve,

Yeah I am taking care of mom too (89), she can't remember anything new and plagued with aches and pains but old memories are hanging in there, she does crossword puzzles every day.

I am saving signs for later as well as decimal point handling. My first goal is the 4 main arithmetic subs going for arbitrary long positive integers. I save the best for last Division, then signs, then decimals...

I know I am reinventing the wheel but you learn inside stuff with DIY. Then if you have a bug or want to handle decimals different you know (maybe if you remember) how to get what you want without having to learn the whole thing anyway using someone else approach. Of course, someone else approach could be much more clever but how do you know if you didn't try it yourself?

I am working on 2 helper functions now and rewriting add$, subtr$, mult$ with lines of code savings! If things work out ;-))

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #19 on: August 16, 2020, 09:57:24 pm »
I think I might have a unique approach for division, preliminary results are looking really good!

I am intending to use multiplicative inverses, ie for n / d, use n * 1/d.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #20 on: August 17, 2020, 01:25:10 am »
Getting divide$() from Multiplicative Inverse worked like a charm for getting nice set of digits but getting the decimal settled at right point (was that a pun?) took a little more doing including not stripping 0's until last, display, step.

Code: QB64: [Select]
  1. _TITLE "Multplicative Inverse: nInverse$() and divide$()" ' b+ 2020-08-16 from
  2. ' I always want to try doing division by multipling the numerator
  3. ' by the multiplicative inverse of denominator.
  4. ' n/d = n * 1/d so how bout a function that does 1/n
  5. ' Then divide$() is pretty easy with mult$
  6.  
  7. RANDOMIZE TIMER 'now that it's seems to be running silent
  8. SCREEN _NEWIMAGE(1200, 700, 32)
  9. _DELAY .25
  10.  
  11. 'special cases or hard cases
  12. PRINT nInverse$("0", 100)
  13. PRINT nInverse$("1", 100)
  14. PRINT nInverse$("2", 100) 'yeah this might work!
  15. PRINT nInverse$("7", 150)
  16. PRINT nInverse$("50", 150) ' good
  17. PRINT nInverse$("999999999", 150) ' oh boy!!!
  18. PRINT nInverse$("11111111111111111111", 150)
  19. PRINT nInverse$("1024", 150)
  20. PRINT nInverse$("717", 150)
  21. PRINT "  OK try some Divisions "
  22. PRINT ".5 ? "; divide$("1", "2") 'well that took some tuning
  23. PRINT ".125 ? "; divide$("1", "8") ' well more tuning!
  24. PRINT ".002 ? "; divide$("1", "500")
  25. PRINT "1.714285R714285... ? "; divide$("12", "7")
  26. PRINT "15555 ? "; divide$("108885", "7") 'close to 15555
  27. PRINT "2048 ? "; divide$("131072", "64") 'powers of 2 here 2048
  28. PRINT: PRINT " press any for some random tests comparing divisions to QB64 values"
  29.  
  30. 'random testing
  31. DIM quotient AS DOUBLE, e1 AS INTEGER, e2 AS INTEGER, num$, den$, divStr$, qbCalc$
  32.     'pick two numbers
  33.     e1 = INT(10 * RND): e2 = INT(10 * RND)
  34.     n = RND * 10 ^ e1: d = RND * 10 ^ e2: quotient = n / d
  35.     num$ = _TRIM$(STR$(n)): den$ = _TRIM$(STR$(d))
  36.     PRINT
  37.     PRINT num$; " divided by "; den$; " ="
  38.     divStr$ = divide$(num$, den$)
  39.     PRINT divStr$; " according to mult$ function we are testing."
  40.     qbCalc$ = _TRIM$(STR$(quotient))
  41.     PRINT qbCalc$; " according to QB64 math"
  42.     ' <<<<<<<<<< stop when find an discrepency
  43.     PRINT "Next divide$() check in 10 secs, or press key,  press escape to quit..."
  44.     SLEEP 10
  45.  
  46. FUNCTION divide$ (n$, d$)
  47.     DIM di$, ndi$, nD AS INTEGER
  48.     IF trim0$(n$) = "0" THEN divide$ = "0": EXIT FUNCTION
  49.     IF trim0$(d$) = "0" THEN divide$ = "div 0": EXIT FUNCTION
  50.     IF trim0$(d$) = "1" THEN divide$ = n$: EXIT FUNCTION
  51.     di$ = nInverse$(d$, 100)
  52.     nD = LEN(di$)
  53.     ndi$ = mult$(n$, di$)
  54.     ndi$ = MID$(ndi$, 1, LEN(ndi$) - nD) + "." + RIGHT$(ndi$, nD)
  55.     divide$ = trim0$(ndi$)
  56.  
  57. FUNCTION nInverse$ (n$, DP AS INTEGER) 'assume decimal at very start of the string of digits returned, no rounding
  58.     DIM m$(1 TO 9), si$, r$, outstr$, d$
  59.     DIM i AS INTEGER
  60.     FOR i = 1 TO 9
  61.         si$ = _TRIM$(STR$(i))
  62.         m$(i) = mult$(si$, n$)
  63.     NEXT
  64.     outstr$ = ""
  65.     IF n$ = "0" THEN nInverse$ = "Div 0": EXIT FUNCTION
  66.     IF n$ = "1" THEN nInverse$ = "1": EXIT FUNCTION
  67.     r$ = "10"
  68.     DO
  69.         WHILE LEFT$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  70.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  71.             IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION 'check if we've reached DP length
  72.             r$ = r$ + "0"
  73.         WEND
  74.         FOR i = 9 TO 1 STEP -1
  75.             IF LTE(m$(i), r$) THEN d$ = _TRIM$(STR$(i)): EXIT FOR
  76.         NEXT
  77.         outstr$ = outstr$ + d$
  78.         IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION
  79.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  80.         IF r$ = "0" THEN nInverse$ = outstr$: EXIT FUNCTION 'found a perfect divisor
  81.         r$ = r$ + "0" 'add another place
  82.     LOOP
  83.  
  84. FUNCTION mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  85.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, dp AS INTEGER
  86.     DIM f18$, f1$, t$, build$, accum$
  87.  
  88.     IF trim0$(a$) = "0" THEN mult$ = "0": EXIT FUNCTION
  89.     IF trim0$(b$) = "0" THEN mult$ = "0": EXIT FUNCTION
  90.     IF trim0$(a$) = "1" THEN mult$ = trim0$(b$): EXIT FUNCTION
  91.     IF trim0$(b$) = "1" THEN mult$ = trim0$(a$): EXIT FUNCTION
  92.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  93.     la = LEN(a$): lb = LEN(b$)
  94.     IF la > lb THEN
  95.         m = INT(la / 18) + 1
  96.         f18$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  97.         f1$ = b$
  98.     ELSE
  99.         m = INT(lb / 18) + 1
  100.         f18$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  101.         f1$ = a$
  102.     END IF
  103.     FOR dp = LEN(f1$) TO 1 STEP -1 'dp = digit position of the f1$
  104.         build$ = "" 'line builder
  105.         co = 0
  106.         'now taking 18 digits at a time Thanks Steve McNeill
  107.         FOR g = 1 TO m
  108.             v18 = VAL(MID$(f18$, m * 18 - g * 18 + 1, 18))
  109.             sd = VAL(MID$(f1$, dp, 1))
  110.             t$ = RIGHT$(STRING$(19, "0") + _TRIM$(STR$(v18 * sd + co)), 19)
  111.             co = VAL(MID$(t$, 1, 1))
  112.             build$ = MID$(t$, 2) + build$
  113.         NEXT g
  114.         IF co THEN build$ = _TRIM$(STR$(co)) + build$
  115.         IF dp = LEN(f1$) THEN
  116.             accum$ = build$
  117.         ELSE
  118.             accum$ = add$(accum$, build$ + STRING$(LEN(f1$) - dp, "0"))
  119.         END IF
  120.     NEXT dp
  121.     mult$ = accum$
  122.  
  123. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  124.     DIM m AS INTEGER, g AS INTEGER, p AS INTEGER
  125.     DIM ts$, tm$, sign$, LG$, sm$, t$, result$
  126.  
  127.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  128.     IF trim0(ts$) = trim0$(tm$) THEN subtr$ = "0": EXIT FUNCTION 'OK proceed with function knowing they are not equal
  129.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  130.     IF LTE(ts$, tm$) THEN ' which is bigger? minus is bigger
  131.         sign$ = "-"
  132.         m = INT(LEN(tm$) / 18) + 1
  133.         LG$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  134.         sm$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  135.     ELSE 'sum is bigger
  136.         sign$ = ""
  137.         m = INT(LEN(ts$) / 18) + 1
  138.         LG$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  139.         sm$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  140.     END IF
  141.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  142.     FOR g = 1 TO m
  143.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  144.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  145.         IF vs > VB THEN
  146.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  147.  
  148.             'debug
  149.             'PRINT VB, tenE18, tenE18 - vs + VB, " t$ = "; t$
  150.  
  151.             ''borrow 1 = rewrite string
  152.             p = (m - g) * 18
  153.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  154.                 MID$(LG$, p, 1) = "9"
  155.                 p = p - 1
  156.             WEND
  157.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  158.         ELSE
  159.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  160.         END IF
  161.         result$ = t$ + result$
  162.     NEXT
  163.     subtr$ = sign$ + result$
  164.  
  165. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  166.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  167.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER
  168.     DIM fa$, fb$, t$, new$, result$
  169.     la = LEN(a$): lb = LEN(b$)
  170.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  171.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  172.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  173.  
  174.     'now taking 18 digits at a time Thanks Steve McNeill
  175.     FOR g = 1 TO m
  176.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  177.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  178.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  179.         co = VAL(MID$(t$, 1, 18))
  180.         new$ = MID$(t$, 19)
  181.         result$ = new$ + result$
  182.  
  183.         'debug
  184.         'DIM w$
  185.         'PRINT a$, m * 18, sa, sb, t$, co, result$
  186.         'INPUT "OK "; w$ 'OK
  187.     NEXT
  188.     IF co THEN result$ = STR$(co) + result$
  189.     add$ = result$
  190.  
  191. ' String Math Helpers -----------------------------------------------
  192.  
  193. 'this function needs TrimLead0$(s$)
  194. FUNCTION LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings
  195.     DIM ca$, cb$, la AS INTEGER, lb AS INTEGER, i AS INTEGER
  196.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  197.     la = LEN(ca$): lb = LEN(cb$)
  198.     IF ca$ = cb$ THEN
  199.         LTE = -1
  200.     ELSEIF la < lb THEN ' a is smaller
  201.         LTE = -1
  202.     ELSEIF la > lb THEN ' a is bigger
  203.         LTE = 0
  204.     ELSEIF la = lb THEN ' equal lengths
  205.         FOR i = 1 TO LEN(ca$)
  206.             IF VAL(MID$(ca$, i, 1)) > VAL(MID$(cb$, i, 1)) THEN
  207.                 LTE = 0: EXIT FUNCTION
  208.             ELSEIF VAL(MID$(ca$, i, 1)) < VAL(MID$(cb$, i, 1)) THEN
  209.                 LTE = -1: EXIT FUNCTION
  210.             END IF
  211.         NEXT
  212.     END IF
  213.  
  214. ' ------------------------------------- use these for final display
  215.  
  216. FUNCTION TrimLead0$ (s$) 'for treating strings as number (pos integers)
  217.     DIM copys$, i AS INTEGER, find AS INTEGER
  218.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  219.     i = 1: find = 0
  220.     WHILE i < LEN(copys$) AND MID$(copys$, i, 1) = "0"
  221.         i = i + 1: find = 1
  222.     WEND
  223.     IF find = 1 THEN copys$ = MID$(copys$, i)
  224.     IF copys$ = "" THEN TrimLead0$ = "0" ELSE TrimLead0$ = copys$
  225.  
  226. FUNCTION TrimTail0$ (s$)
  227.     DIM copys$, dp AS INTEGER, i AS INTEGER, find AS INTEGER
  228.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  229.     TrimTail0$ = copys$
  230.     dp = INSTR(copys$, ".")
  231.     IF dp > 0 THEN
  232.         i = LEN(copys$): find = 0
  233.         WHILE i > dp AND MID$(copys$, i, 1) = "0"
  234.             i = i - 1: find = 1
  235.         WEND
  236.         IF find = 1 THEN
  237.             IF i = dp THEN
  238.                 TrimTail0$ = MID$(copys$, 1, dp - 1)
  239.             ELSE
  240.                 TrimTail0$ = MID$(copys$, 1, i)
  241.             END IF
  242.         END IF
  243.     END IF
  244.  
  245. FUNCTION trim0$ (s$)
  246.     DIM t$
  247.     t$ = TrimLead0$(s$)
  248.     trim0$ = TrimTail0$(t$)
  249.  

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
Re: String Math
« Reply #21 on: August 17, 2020, 03:16:17 am »
Here's a test for ya from http://wfbarnes.net/sxript/docs/fibonacci2/main.php#Top

Quote
It turns out that the first hundred (or so) Fibonacci Numbers can be produced by inverting the integer 999999999999999999999998999999999999999999999999 (bonus points for noticing that one of the digits is not a 9).

Code: [Select]
print_
  largediv(
    1.(for(<i,1,2495,1>,{0})),
    999999999999999999999998999999999999999999999999
  )

Code: [Select]
+0.000000000000000000000000000000000000000000000001000000000000000000000001000000000000000000000002000000000000000000000003000000000000000000000005000000000000000000000008000000000000000000000013000000000000000000000021000000000000000000000034000000000000000000000055000000000000000000000089000000000000000000000144000000000000000000000233000000000000000000000377000000000000000000000610000000000000000000000987000000000000000000001597000000000000000000002584000000000000000000004181000000000000000000006765000000000000000000010946000000000000000000017711000000000000000000028657000000000000000000046368000000000000000000075025000000000000000000121393000000000000000000196418000000000000000000317811000000000000000000514229000000000000000000832040000000000000000001346269000000000000000002178309000000000000000003524578000000000000000005702887000000000000000009227465000000000000000014930352000000000000000024157817000000000000000039088169000000000000000063245986000000000000000102334155000000000000000165580141000000000000000267914296000000000000000433494437000000000000000701408733000000000000001134903170000000000000001836311903000000000000002971215073000000000000004807526976000000000000007778742049000000000000012586269025000000000000020365011074000000000000032951280099000000000000053316291173000000000000086267571272000000000000139583862445000000000000225851433717000000000000365435296162000000000000591286729879000000000000956722026041000000000001548008755920000000000002504730781961000000000004052739537881000000000006557470319842000000000010610209857723000000000017167680177565000000000027777890035288000000000044945570212853000000000072723460248141000000000117669030460994000000000190392490709135000000000308061521170129000000000498454011879264000000000806515533049393000000001304969544928657000000002111485077978050000000003416454622906707000000005527939700884757000000008944394323791464000000014472334024676221000000023416728348467685000000037889062373143906000000061305790721611591000000099194853094755497000000160500643816367088000000259695496911122585000000420196140727489673000000679891637638612258000001100087778366101931000001779979416004714189000002880067194370816120000004660046610375530309000007540113804746346429000012200160415121876738000019740274219868223167000031940434634990099905000051680708854858323072000083621143489848422977000135301852344706746049000218922995834555169026000354224848179261915075000573147844013817084101000927372692193078999176001500520536206896083277002427893228399975082453003928413764606871165730006356306993006846248183010284720757613717413913
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #22 on: August 17, 2020, 09:20:09 am »
Well there is an interesting experiment! Thanx STx :)

The first order of business today was fixing the nInverse function with a decimal point because I need to return 1 (without decimal) when n = 1

2nd thing is rounding when I get a repeating stream of 9's for divide, usually it means it should be infinitesimally small nudge more.
« Last Edit: August 17, 2020, 09:26:08 am by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #23 on: August 17, 2020, 11:56:07 am »
Some nice results this morning, tweaking last nights nInverse$(), divide$, adding showDP$() for rounding at DP + 1 if have that many decimals.

Code: QB64: [Select]
  1. _TITLE "Multplicative Inverse: nInverse$() and divide$()" ' b+ 2020-08-16
  2. ' 2020-08-16 picking up from Sting Math add$(), subtr$(), mult$() onto divide$()
  3. ' I always wanted to try doing division by multipling the numerator
  4. ' by the multiplicative inverse of denominator.
  5. ' n/d = n * 1/d so how bout a function that does 1/n.
  6. ' Then divide$() is pretty easy with mult$  Yes works but need more decimal work.
  7. ' 2020-08-17 redo all this with a decimal point for nInverse just start outstr with "." instead ""
  8. ' Now somethings off with divide$() first is 1 / 500 should be .002 returning .2, debug."
  9. ' Much better than post of last night
  10. ' showDP rounds up to DP (Decimal Places IF there are more decimals past DP)
  11.  
  12. RANDOMIZE TIMER 'now that it's seems to be running silent
  13. SCREEN _NEWIMAGE(1200, 700, 32)
  14. _DELAY .25
  15.  
  16. 'special cases or hard cases
  17. PRINT "div 0 ? "; nInverse$("0", 100)
  18. PRINT "This is 2nd special case where no decimal is returned: 1 ? "; nInverse$("1", 100)
  19. PRINT "1/2 =.5 no more no less ? "; nInverse$("2", 100) 'yeah this might work!
  20. PRINT ".142857 and repeat forever ? for 1/7"
  21. PRINT nInverse$("7", 149)
  22. PRINT ".02 no more or less ? "; nInverse$("50", 149) ' good
  23. PRINT "1/ (9 nines) should b"
  24. PRINT ".000000001 repeated forever ?"
  25. PRINT nInverse$("999999999", 149) ' oh boy!!!
  26. PRINT "1/ (20 ones) should be 19 zeros nine repeated forever ?"
  27. PRINT ".00000000000000000009 repeat forever ?"
  28. PRINT nInverse$("11111111111111111111", 149)
  29. PRINT ".0009765625 ?"
  30. PRINT nInverse$("1024", 150)
  31. PRINT ".0013947 repeat forever ?"
  32. PRINT nInverse$("717", 150)
  33. PRINT "  OK try some Divisions "
  34. PRINT ".5 ? "; divide$("1", "2") 'well that took some tuning
  35. PRINT ".125 ? "; divide$("1", "8") ' well more tuning!
  36. PRINT ".002 ? "; divide$("1", "500") 'lost first 2 zeros?
  37. PRINT ".002222...? "; divide$("1", "450")
  38. PRINT "1.002222...? "; divide$("451", "450")
  39. PRINT "1.714285R714285... ? "; divide$("12", "7")
  40. PRINT "15555 ? "; divide$("108885", "7") 'close to 15555
  41. PRINT "test showDP fix 9's to 4 places?"
  42. PRINT "15555 ? "; showDP(divide$("108885", "7"), 4)
  43. PRINT "2048 ? "; divide$("131072", "64") 'powers of 2 here 2048
  44. PRINT "1 ? "; showDP(divide$("99999", "100000"), 2)
  45. PRINT: PRINT " press any for some random tests comparing divisions to QB64 values"
  46.  
  47. 'random testing
  48. DIM quotient AS DOUBLE, e1 AS INTEGER, e2 AS INTEGER, num$, den$, divStr$, qbCalc$
  49.     'pick two numbers
  50.     e1 = INT(10 * RND): e2 = INT(10 * RND)
  51.     n = RND * 10 ^ e1: d = RND * 10 ^ e2: quotient = n / d
  52.     num$ = _TRIM$(STR$(n)): den$ = _TRIM$(STR$(d))
  53.     PRINT
  54.     PRINT num$; " divided by "; den$; " ="
  55.     divStr$ = divide$(num$, den$)
  56.     PRINT showDP$(divStr$, 60); " LEN:"; LEN(showDP$(divStr$, 60)); " divide$() trunc. 60"
  57.     qbCalc$ = _TRIM$(STR$(quotient))
  58.     PRINT qbCalc$; " according to QB64 math"
  59.     ' <<<<<<<<<< stop when find an discrepency
  60.     PRINT "Next divide$() check in 10 secs, or press key,  press escape to quit..."
  61.     SLEEP 10
  62.  
  63. FUNCTION divide$ (n$, d$)
  64.     DIM di$, ndi$, nD AS INTEGER
  65.     IF trim0$(n$) = "0" THEN divide$ = "0": EXIT FUNCTION
  66.     IF trim0$(d$) = "0" THEN divide$ = "div 0": EXIT FUNCTION
  67.     IF trim0$(d$) = "1" THEN divide$ = trim0$(n$): EXIT FUNCTION '8/17 add trim0$
  68.     di$ = MID$(nInverse$(d$, 100), 2) 'chop off decimal point after
  69.     nD = LEN(di$)
  70.     ndi$ = mult$(n$, di$)
  71.     ndi$ = MID$(ndi$, 1, LEN(ndi$) - nD) + "." + RIGHT$(STRING$(nD, "0") + RIGHT$(ndi$, nD), nD)
  72.     divide$ = trim0$(ndi$)
  73.  
  74. FUNCTION nInverse$ (n$, DP AS INTEGER) 'assume decimal at very start of the string of digits returned, no rounding
  75.     DIM m$(1 TO 9), si$, r$, outstr$, d$
  76.     DIM i AS INTEGER
  77.     FOR i = 1 TO 9
  78.         si$ = _TRIM$(STR$(i))
  79.         m$(i) = mult$(si$, n$)
  80.     NEXT
  81.     outstr$ = ""
  82.     IF n$ = "0" THEN nInverse$ = "Div 0": EXIT FUNCTION
  83.     IF n$ = "1" THEN nInverse$ = "1": EXIT FUNCTION
  84.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  85.     r$ = "10"
  86.     DO
  87.         WHILE LEFT$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  88.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  89.             IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION 'check if we've reached DP length
  90.             r$ = r$ + "0"
  91.         WEND
  92.         FOR i = 9 TO 1 STEP -1
  93.             IF LTE(m$(i), r$) THEN d$ = _TRIM$(STR$(i)): EXIT FOR
  94.         NEXT
  95.         outstr$ = outstr$ + d$
  96.         IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION
  97.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  98.         IF r$ = "0" THEN nInverse$ = outstr$: EXIT FUNCTION 'found a perfect divisor
  99.         r$ = r$ + "0" 'add another place
  100.     LOOP
  101.  
  102. FUNCTION mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  103.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, dp AS INTEGER
  104.     DIM f18$, f1$, t$, build$, accum$
  105.  
  106.     IF trim0$(a$) = "0" THEN mult$ = "0": EXIT FUNCTION
  107.     IF trim0$(b$) = "0" THEN mult$ = "0": EXIT FUNCTION
  108.     IF trim0$(a$) = "1" THEN mult$ = trim0$(b$): EXIT FUNCTION
  109.     IF trim0$(b$) = "1" THEN mult$ = trim0$(a$): EXIT FUNCTION
  110.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  111.     la = LEN(a$): lb = LEN(b$)
  112.     IF la > lb THEN
  113.         m = INT(la / 18) + 1
  114.         f18$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  115.         f1$ = b$
  116.     ELSE
  117.         m = INT(lb / 18) + 1
  118.         f18$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  119.         f1$ = a$
  120.     END IF
  121.     FOR dp = LEN(f1$) TO 1 STEP -1 'dp = digit position of the f1$
  122.         build$ = "" 'line builder
  123.         co = 0
  124.         'now taking 18 digits at a time Thanks Steve McNeill
  125.         FOR g = 1 TO m
  126.             v18 = VAL(MID$(f18$, m * 18 - g * 18 + 1, 18))
  127.             sd = VAL(MID$(f1$, dp, 1))
  128.             t$ = RIGHT$(STRING$(19, "0") + _TRIM$(STR$(v18 * sd + co)), 19)
  129.             co = VAL(MID$(t$, 1, 1))
  130.             build$ = MID$(t$, 2) + build$
  131.         NEXT g
  132.         IF co THEN build$ = _TRIM$(STR$(co)) + build$
  133.         IF dp = LEN(f1$) THEN
  134.             accum$ = build$
  135.         ELSE
  136.             accum$ = add$(accum$, build$ + STRING$(LEN(f1$) - dp, "0"))
  137.         END IF
  138.     NEXT dp
  139.     mult$ = accum$
  140.  
  141. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  142.     DIM m AS INTEGER, g AS INTEGER, p AS INTEGER
  143.     DIM ts$, tm$, sign$, LG$, sm$, t$, result$
  144.  
  145.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  146.     IF trim0(ts$) = trim0$(tm$) THEN subtr$ = "0": EXIT FUNCTION 'OK proceed with function knowing they are not equal
  147.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  148.     IF LTE(ts$, tm$) THEN ' which is bigger? minus is bigger
  149.         sign$ = "-"
  150.         m = INT(LEN(tm$) / 18) + 1
  151.         LG$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  152.         sm$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  153.     ELSE 'sum is bigger
  154.         sign$ = ""
  155.         m = INT(LEN(ts$) / 18) + 1
  156.         LG$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  157.         sm$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  158.     END IF
  159.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  160.     FOR g = 1 TO m
  161.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  162.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  163.         IF vs > VB THEN
  164.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  165.  
  166.             'debug
  167.             'PRINT VB, tenE18, tenE18 - vs + VB, " t$ = "; t$
  168.  
  169.             ''borrow 1 = rewrite string
  170.             p = (m - g) * 18
  171.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  172.                 MID$(LG$, p, 1) = "9"
  173.                 p = p - 1
  174.             WEND
  175.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  176.         ELSE
  177.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  178.         END IF
  179.         result$ = t$ + result$
  180.     NEXT
  181.     subtr$ = sign$ + result$
  182.  
  183. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  184.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  185.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER
  186.     DIM fa$, fb$, t$, new$, result$
  187.     la = LEN(a$): lb = LEN(b$)
  188.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  189.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  190.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  191.  
  192.     'now taking 18 digits at a time Thanks Steve McNeill
  193.     FOR g = 1 TO m
  194.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  195.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  196.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  197.         co = VAL(MID$(t$, 1, 18))
  198.         new$ = MID$(t$, 19)
  199.         result$ = new$ + result$
  200.  
  201.         'debug
  202.         'DIM w$
  203.         'PRINT a$, m * 18, sa, sb, t$, co, result$
  204.         'INPUT "OK "; w$ 'OK
  205.     NEXT
  206.     IF co THEN result$ = STR$(co) + result$
  207.     add$ = result$
  208.  
  209. ' String Math Helpers -----------------------------------------------
  210.  
  211. 'this function needs TrimLead0$(s$)
  212. FUNCTION LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings
  213.     DIM ca$, cb$, la AS INTEGER, lb AS INTEGER, i AS INTEGER
  214.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  215.     la = LEN(ca$): lb = LEN(cb$)
  216.     IF ca$ = cb$ THEN
  217.         LTE = -1
  218.     ELSEIF la < lb THEN ' a is smaller
  219.         LTE = -1
  220.     ELSEIF la > lb THEN ' a is bigger
  221.         LTE = 0
  222.     ELSEIF la = lb THEN ' equal lengths
  223.         FOR i = 1 TO LEN(ca$)
  224.             IF VAL(MID$(ca$, i, 1)) > VAL(MID$(cb$, i, 1)) THEN
  225.                 LTE = 0: EXIT FUNCTION
  226.             ELSEIF VAL(MID$(ca$, i, 1)) < VAL(MID$(cb$, i, 1)) THEN
  227.                 LTE = -1: EXIT FUNCTION
  228.             END IF
  229.         NEXT
  230.     END IF
  231.  
  232. ' ------------------------------------- use these for final display
  233.  
  234. FUNCTION TrimLead0$ (s$) 'for treating strings as number (pos integers)
  235.     DIM copys$, i AS INTEGER, find AS INTEGER
  236.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  237.     i = 1: find = 0
  238.     WHILE i < LEN(copys$) AND MID$(copys$, i, 1) = "0"
  239.         i = i + 1: find = 1
  240.     WEND
  241.     IF find = 1 THEN copys$ = MID$(copys$, i)
  242.     IF copys$ = "" THEN TrimLead0$ = "0" ELSE TrimLead0$ = copys$
  243.  
  244. FUNCTION TrimTail0$ (s$)
  245.     DIM copys$, dp AS INTEGER, i AS INTEGER, find AS INTEGER
  246.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  247.     TrimTail0$ = copys$
  248.     dp = INSTR(copys$, ".")
  249.     IF dp > 0 THEN
  250.         i = LEN(copys$): find = 0
  251.         WHILE i > dp AND MID$(copys$, i, 1) = "0"
  252.             i = i - 1: find = 1
  253.         WEND
  254.         IF find = 1 THEN
  255.             IF i = dp THEN
  256.                 TrimTail0$ = MID$(copys$, 1, dp - 1)
  257.             ELSE
  258.                 TrimTail0$ = MID$(copys$, 1, i)
  259.             END IF
  260.         END IF
  261.     END IF
  262.  
  263. FUNCTION trim0$ (s$)
  264.     DIM t$
  265.     t$ = TrimLead0$(s$)
  266.     trim0$ = TrimTail0$(t$)
  267.  
  268. ' for displaying truncated numbers say to 60 digits
  269. FUNCTION showDP$ (num$, nDP AS INTEGER)
  270.     DIM cNum$, dp AS INTEGER, d AS INTEGER, i AS INTEGER
  271.     cNum$ = num$ 'since num$ could get changed
  272.     showDP$ = num$
  273.     dp = INSTR(num$, ".")
  274.     IF dp > 0 THEN
  275.         IF LEN(MID$(cNum$, dp + 1)) > nDP THEN
  276.             d = VAL(MID$(cNum$, dp + nDP + 1, 1))
  277.             IF d > 4 THEN
  278.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  279.                 dp = dp + 1
  280.                 i = dp + nDP
  281.                 WHILE MID$(cNum$, i, 1) = "9" OR MID$(cNum$, i, 1) = "."
  282.                     IF MID$(cNum$, i, 1) = "9" THEN
  283.                         MID$(cNum$, i, 1) = "0"
  284.                     END IF
  285.                     i = i - 1
  286.                 WEND
  287.                 MID$(cNum$, i, 1) = _TRIM$(STR$(VAL(MID$(cNum$, i, 1)) + 1)) 'last non 9 digit
  288.                 cNum$ = MID$(cNum$, 1, dp + nDP) 'chop it
  289.                 showDP$ = trim0$(cNum$)
  290.             ELSE
  291.                 showDP$ = MID$(cNum$, 1, dp + nDP)
  292.             END IF
  293.         END IF
  294.     END IF
  295.  

Milestone #1 the 4 Basic Arithmetic operations, plus display control of number (wo changing original number string).

Now for some fun with STx quiz ;-))

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #24 on: August 17, 2020, 12:44:30 pm »
102 terms of sequence from inverse of STx Number:
(note: _UNSIGNED _INTEGER64 couldn't go the distance.)

Code: QB64: [Select]
  1. _TITLE "Multplicative Inverse: STx Fibonacci " ' b+ 2020-08-17
  2. ' 2020-08-16 picking up from Sting Math add$(), subtr$(), mult$() onto divide$()
  3. ' I always wanted to try doing division by multipling the numerator
  4. ' by the multiplicative inverse of denominator.
  5. ' n/d = n * 1/d so how bout a function that does 1/n.
  6. ' Then divide$() is pretty easy with mult$  Yes works but need more decimal work.
  7. ' 2020-08-17 redo all this with a decimal point for nInverse just start outstr with "." instead ""
  8. ' Now somethings off with divide$() first is 1 / 500 should be .002 returning .2, debug."
  9. ' Much better than post of last night
  10. ' showDP rounds up to DP (Decimal Places IF there are more decimals past DP)
  11.  
  12. ' 2020-08-17 from Multplicative Inverse: nInverse$ and divide$
  13. ' STx Fibonacci from
  14. ' ref: https://www.qb64.org/forum/index.php?topic=2921.msg121845#msg121845
  15.  
  16.  
  17. RANDOMIZE TIMER 'now that it's seems to be running silent
  18. SCREEN _NEWIMAGE(1200, 700, 32)
  19. _DELAY .25
  20.  
  21. 'special cases or hard cases
  22. DIM STxFib$
  23. STxFib$ = nInverse$("999999999999999999999998999999999999999999999999", 2495) '2445 DP? is what he has
  24. PRINT STxFib$
  25. 'OK starts good let's parse it
  26. DIM fibi(1 TO 200) AS STRING
  27. i = 3
  28. fibi(1) = "1"
  29. fibi(2) = "1"
  30. find = 0
  31.     fibi(i) = trim0$(add$(fibi(i - 2), fibi(i - 1)))
  32.     find = INSTR(find + 1, STxFib$, fibi(i))
  33.     IF find > 0 THEN
  34.         PRINT "Found Fibonacci"; i; ", "; fibi(i); " at"; find; "of len(STxFib$) ="; LEN(STxFib$)
  35.         i = i + 1
  36.     ELSE
  37.         PRINT "Failed to find next Fibonacci number"; i
  38.         dont = -1
  39.     END IF
  40.     PRINT "Press any to continue..."
  41.     SLEEP
  42. LOOP UNTIL dont = -1
  43.  
  44.  
  45.  
  46.  
  47. FUNCTION divide$ (n$, d$)
  48.     DIM di$, ndi$, nD AS INTEGER
  49.     IF trim0$(n$) = "0" THEN divide$ = "0": EXIT FUNCTION
  50.     IF trim0$(d$) = "0" THEN divide$ = "div 0": EXIT FUNCTION
  51.     IF trim0$(d$) = "1" THEN divide$ = trim0$(n$): EXIT FUNCTION '8/17 add trim0$
  52.     di$ = MID$(nInverse$(d$, 100), 2) 'chop off decimal point after
  53.     nD = LEN(di$)
  54.     ndi$ = mult$(n$, di$)
  55.     ndi$ = MID$(ndi$, 1, LEN(ndi$) - nD) + "." + RIGHT$(STRING$(nD, "0") + RIGHT$(ndi$, nD), nD)
  56.     divide$ = trim0$(ndi$)
  57.  
  58. FUNCTION nInverse$ (n$, DP AS INTEGER) 'assume decimal at very start of the string of digits returned, no rounding
  59.     DIM m$(1 TO 9), si$, r$, outstr$, d$
  60.     DIM i AS INTEGER
  61.     FOR i = 1 TO 9
  62.         si$ = _TRIM$(STR$(i))
  63.         m$(i) = mult$(si$, n$)
  64.     NEXT
  65.     outstr$ = ""
  66.     IF n$ = "0" THEN nInverse$ = "Div 0": EXIT FUNCTION
  67.     IF n$ = "1" THEN nInverse$ = "1": EXIT FUNCTION
  68.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  69.     r$ = "10"
  70.     DO
  71.         WHILE LEFT$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  72.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  73.             IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION 'check if we've reached DP length
  74.             r$ = r$ + "0"
  75.         WEND
  76.         FOR i = 9 TO 1 STEP -1
  77.             IF LTE(m$(i), r$) THEN d$ = _TRIM$(STR$(i)): EXIT FOR
  78.         NEXT
  79.         outstr$ = outstr$ + d$
  80.         IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION
  81.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  82.         IF r$ = "0" THEN nInverse$ = outstr$: EXIT FUNCTION 'found a perfect divisor
  83.         r$ = r$ + "0" 'add another place
  84.     LOOP
  85.  
  86. FUNCTION mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  87.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, dp AS INTEGER
  88.     DIM f18$, f1$, t$, build$, accum$
  89.  
  90.     IF trim0$(a$) = "0" THEN mult$ = "0": EXIT FUNCTION
  91.     IF trim0$(b$) = "0" THEN mult$ = "0": EXIT FUNCTION
  92.     IF trim0$(a$) = "1" THEN mult$ = trim0$(b$): EXIT FUNCTION
  93.     IF trim0$(b$) = "1" THEN mult$ = trim0$(a$): EXIT FUNCTION
  94.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  95.     la = LEN(a$): lb = LEN(b$)
  96.     IF la > lb THEN
  97.         m = INT(la / 18) + 1
  98.         f18$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  99.         f1$ = b$
  100.     ELSE
  101.         m = INT(lb / 18) + 1
  102.         f18$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  103.         f1$ = a$
  104.     END IF
  105.     FOR dp = LEN(f1$) TO 1 STEP -1 'dp = digit position of the f1$
  106.         build$ = "" 'line builder
  107.         co = 0
  108.         'now taking 18 digits at a time Thanks Steve McNeill
  109.         FOR g = 1 TO m
  110.             v18 = VAL(MID$(f18$, m * 18 - g * 18 + 1, 18))
  111.             sd = VAL(MID$(f1$, dp, 1))
  112.             t$ = RIGHT$(STRING$(19, "0") + _TRIM$(STR$(v18 * sd + co)), 19)
  113.             co = VAL(MID$(t$, 1, 1))
  114.             build$ = MID$(t$, 2) + build$
  115.         NEXT g
  116.         IF co THEN build$ = _TRIM$(STR$(co)) + build$
  117.         IF dp = LEN(f1$) THEN
  118.             accum$ = build$
  119.         ELSE
  120.             accum$ = add$(accum$, build$ + STRING$(LEN(f1$) - dp, "0"))
  121.         END IF
  122.     NEXT dp
  123.     mult$ = accum$
  124.  
  125. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  126.     DIM m AS INTEGER, g AS INTEGER, p AS INTEGER
  127.     DIM ts$, tm$, sign$, LG$, sm$, t$, result$
  128.  
  129.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  130.     IF trim0(ts$) = trim0$(tm$) THEN subtr$ = "0": EXIT FUNCTION 'OK proceed with function knowing they are not equal
  131.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  132.     IF LTE(ts$, tm$) THEN ' which is bigger? minus is bigger
  133.         sign$ = "-"
  134.         m = INT(LEN(tm$) / 18) + 1
  135.         LG$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  136.         sm$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  137.     ELSE 'sum is bigger
  138.         sign$ = ""
  139.         m = INT(LEN(ts$) / 18) + 1
  140.         LG$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  141.         sm$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  142.     END IF
  143.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  144.     FOR g = 1 TO m
  145.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  146.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  147.         IF vs > VB THEN
  148.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  149.  
  150.             'debug
  151.             'PRINT VB, tenE18, tenE18 - vs + VB, " t$ = "; t$
  152.  
  153.             ''borrow 1 = rewrite string
  154.             p = (m - g) * 18
  155.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  156.                 MID$(LG$, p, 1) = "9"
  157.                 p = p - 1
  158.             WEND
  159.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  160.         ELSE
  161.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  162.         END IF
  163.         result$ = t$ + result$
  164.     NEXT
  165.     subtr$ = sign$ + result$
  166.  
  167. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  168.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  169.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER
  170.     DIM fa$, fb$, t$, new$, result$
  171.     la = LEN(a$): lb = LEN(b$)
  172.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  173.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  174.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  175.  
  176.     'now taking 18 digits at a time Thanks Steve McNeill
  177.     FOR g = 1 TO m
  178.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  179.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  180.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  181.         co = VAL(MID$(t$, 1, 18))
  182.         new$ = MID$(t$, 19)
  183.         result$ = new$ + result$
  184.  
  185.         'debug
  186.         'DIM w$
  187.         'PRINT a$, m * 18, sa, sb, t$, co, result$
  188.         'INPUT "OK "; w$ 'OK
  189.     NEXT
  190.     IF co THEN result$ = STR$(co) + result$
  191.     add$ = result$
  192.  
  193. ' String Math Helpers -----------------------------------------------
  194.  
  195. 'this function needs TrimLead0$(s$)
  196. FUNCTION LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings
  197.     DIM ca$, cb$, la AS INTEGER, lb AS INTEGER, i AS INTEGER
  198.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  199.     la = LEN(ca$): lb = LEN(cb$)
  200.     IF ca$ = cb$ THEN
  201.         LTE = -1
  202.     ELSEIF la < lb THEN ' a is smaller
  203.         LTE = -1
  204.     ELSEIF la > lb THEN ' a is bigger
  205.         LTE = 0
  206.     ELSEIF la = lb THEN ' equal lengths
  207.         FOR i = 1 TO LEN(ca$)
  208.             IF VAL(MID$(ca$, i, 1)) > VAL(MID$(cb$, i, 1)) THEN
  209.                 LTE = 0: EXIT FUNCTION
  210.             ELSEIF VAL(MID$(ca$, i, 1)) < VAL(MID$(cb$, i, 1)) THEN
  211.                 LTE = -1: EXIT FUNCTION
  212.             END IF
  213.         NEXT
  214.     END IF
  215.  
  216. ' ------------------------------------- use these for final display
  217.  
  218. FUNCTION TrimLead0$ (s$) 'for treating strings as number (pos integers)
  219.     DIM copys$, i AS INTEGER, find AS INTEGER
  220.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  221.     i = 1: find = 0
  222.     WHILE i < LEN(copys$) AND MID$(copys$, i, 1) = "0"
  223.         i = i + 1: find = 1
  224.     WEND
  225.     IF find = 1 THEN copys$ = MID$(copys$, i)
  226.     IF copys$ = "" THEN TrimLead0$ = "0" ELSE TrimLead0$ = copys$
  227.  
  228. FUNCTION TrimTail0$ (s$)
  229.     DIM copys$, dp AS INTEGER, i AS INTEGER, find AS INTEGER
  230.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  231.     TrimTail0$ = copys$
  232.     dp = INSTR(copys$, ".")
  233.     IF dp > 0 THEN
  234.         i = LEN(copys$): find = 0
  235.         WHILE i > dp AND MID$(copys$, i, 1) = "0"
  236.             i = i - 1: find = 1
  237.         WEND
  238.         IF find = 1 THEN
  239.             IF i = dp THEN
  240.                 TrimTail0$ = MID$(copys$, 1, dp - 1)
  241.             ELSE
  242.                 TrimTail0$ = MID$(copys$, 1, i)
  243.             END IF
  244.         END IF
  245.     END IF
  246.  
  247. FUNCTION trim0$ (s$)
  248.     DIM t$
  249.     t$ = TrimLead0$(s$)
  250.     trim0$ = TrimTail0$(t$)
  251.  
  252. ' for displaying truncated numbers say to 60 digits
  253. FUNCTION showDP$ (num$, nDP AS INTEGER)
  254.     DIM cNum$, dp AS INTEGER, d AS INTEGER, i AS INTEGER
  255.     cNum$ = num$ 'since num$ could get changed
  256.     showDP$ = num$
  257.     dp = INSTR(num$, ".")
  258.     IF dp > 0 THEN
  259.         IF LEN(MID$(cNum$, dp + 1)) > nDP THEN
  260.             d = VAL(MID$(cNum$, dp + nDP + 1, 1))
  261.             IF d > 4 THEN
  262.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  263.                 dp = dp + 1
  264.                 i = dp + nDP
  265.                 WHILE MID$(cNum$, i, 1) = "9" OR MID$(cNum$, i, 1) = "."
  266.                     IF MID$(cNum$, i, 1) = "9" THEN
  267.                         MID$(cNum$, i, 1) = "0"
  268.                     END IF
  269.                     i = i - 1
  270.                 WEND
  271.                 MID$(cNum$, i, 1) = _TRIM$(STR$(VAL(MID$(cNum$, i, 1)) + 1)) 'last non 9 digit
  272.                 cNum$ = MID$(cNum$, 1, dp + nDP) 'chop it
  273.                 showDP$ = trim0$(cNum$)
  274.             ELSE
  275.                 showDP$ = MID$(cNum$, 1, dp + nDP)
  276.             END IF
  277.         END IF
  278.     END IF
  279.  

 
STxFib from Mult Inv.PNG
« Last Edit: August 17, 2020, 12:49:40 pm by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #25 on: August 18, 2020, 03:40:27 pm »
Here it is! String math for 4 basic arithmetic operations with signs and decimal places in under 300 lines:

Code: QB64: [Select]
  1. _TITLE "Math regulator mr" ' b+ start 2020-08-17
  2. ' 2020-08-16 picking up from String Math add$(), subtr$(), mult$() onto divide$()
  3. ' I always wanted to try doing division by multipling the numerator
  4. ' by the multiplicative inverse of denominator.
  5. ' n/d = n * 1/d so how bout a function that does 1/n.
  6. ' Then divide$() is pretty easy with mult$  Yes works but need more decimal work.
  7. ' 2020-08-17 redo all this with a decimal point for nInverse just start outstr with "." instead ""
  8. ' Now somethings off with divide$() first is 1 / 500 should be .002 returning .2, debug."
  9. ' Much better than post of last night
  10. ' showDP rounds up to DP (Decimal Places IF there are more decimals past DP)
  11. ' 2020-08-17 Math regulator mr$(a$, ops$, b$) ' manages before and after calls to 4 basic arith operations
  12. ' note: 265 lines with just the set of functions so far
  13. ' 2020-08-18 last night subtr and signs confused hell out of me but I think I have all code I need for +!
  14. ' OK add and subtr seem to be working with signs and decimals but ugly code, can we combine lines?
  15. ' OH yeah, traded 12 redundant lines for 2! and a bunch of debug and other 389 lines to 354
  16. ' OK setup for * and /, mr$ finished adding 77 lines of code
  17. ' OK everything is simple after divide$() and substraction with signs and decimals ;-))
  18.  
  19. RANDOMIZE TIMER 'now that it's seems to be running silent
  20. SCREEN _NEWIMAGE(1200, 700, 32)
  21. _DELAY .25
  22.  
  23. ' debug tests
  24. 'PRINT "2105 ? "
  25. 'PRINT mr$("5", "+", "2100") ' OK
  26. 'PRINT "-10039.0965 ? "
  27. 'PRINT mr$("6.1035", "+", "-10045.2") ' OK
  28. 'PRINT "-801.99968 ?"
  29. 'PRINT mr$("-802", "+", ".00032")
  30. 'PRINT "-.0003500009 ?"
  31. 'PRINT mr$("-.00035", "+", "-.0000000009")
  32. 'PRINT "-5 ?"
  33. 'PRINT mr$("-10", "-", "-5")
  34. 'PRINT "-15 ?"
  35. 'PRINT mr$("-10", "-", "5") 'X fixed
  36. 'PRINT "15 ?"
  37. 'PRINT mr$("10", "-", "-5")
  38. 'PRINT "5 ?"
  39. 'PRINT mr$("10", "-", "5")
  40. 'PRINT "4.99 ?"
  41. 'PRINT mr$("-.010", "-", "-5")
  42. 'PRINT "-5.01 ?"
  43. 'PRINT mr$("-.010", "-", "5") 'X  fixed
  44. 'PRINT "5.01 ?"
  45. 'PRINT mr$(".010", "-", "-5")
  46. 'PRINT "-4.99 ?"
  47. 'PRINT mr$(".010", "-", "5")
  48. 'PRINT .00207 * 10000; "?"
  49. 'PRINT mr$(".00207", " * ", "10000") 'OK
  50. 'PRINT 2468.02468 / .02; "?"
  51. 'PRINT mr$("2468.02468", "/", ".02") 'OK
  52. 'PRINT -.333333333333333333 * -3.00000000000; "?"
  53. 'PRINT mr$("-.333333333333333333", "*", "-3.00000000000") 'OK
  54. 'PRINT -1.0000001 / .07; "?"
  55. 'PRINT mr$("-1.0000001", "/", ".07") 'OK
  56. 'PRINT 90.9 / -30; "?"
  57. 'PRINT mr$("90.9", "/", "-30") 'OK
  58.  
  59.  
  60. 'random testing for correct signs, decimal places and values
  61. DIM ad AS DOUBLE, bd AS DOUBLE, check AS DOUBLE, ea AS INTEGER, eb AS INTEGER, ma AS INTEGER, mb AS INTEGER
  62. DIM op$, a$, b$, mrStr$, ma$, mb$, qbCalc$
  63.     'pick two numbers
  64.     op$ = MID$("+-*/", INT(RND * 4) + 1, 1)
  65.     IF RND < .5 THEN
  66.         ma$ = "-": ma = -1
  67.     ELSE
  68.         ma$ = "": ma = 1
  69.     END IF
  70.     IF RND < .5 THEN
  71.         mb$ = "-": mb = -1
  72.     ELSE
  73.         mb$ = "": mb = 1
  74.     END IF
  75.  
  76.     ea = INT(10 * RND): eb = INT(10 * RND)
  77.     ad = ma * RND * 10 ^ ea: bd = mb * RND * 10 ^ eb
  78.     SELECT CASE op$
  79.         CASE "+": check = ad + bd
  80.         CASE "-": check = ad - bd
  81.         CASE "*": check = ad * bd
  82.         CASE "/": check = ad / bd
  83.     END SELECT
  84.     a$ = _TRIM$(STR$(ad)): b$ = _TRIM$(STR$(bd))
  85.     PRINT
  86.     PRINT a$; " "; op$; " "; b$; " ="
  87.     mrStr$ = mr$(a$, op$, b$)
  88.     PRINT mrStr$; " according to mr$ function we are testing."
  89.     qbCalc$ = _TRIM$(STR$(check))
  90.     PRINT qbCalc$; " according to QB64 math"
  91.     PRINT "diff ="; VAL(mrStr$) - check
  92.     PRINT "Next subtr$ check in 10 secs, or press key,  press escape to quit..."
  93.     SLEEP 10 ' comment out to see if any delay in execution of print
  94.  
  95. FUNCTION mr$ (a$, op$, b$) ' catchy? mr$ for math regulator
  96.     DIM ca$, cb$, aSgn$, bSgn$, postOp$, sgn$
  97.     DIM adp AS INTEGER, bdp AS INTEGER, dp AS INTEGER, lpop AS INTEGER
  98.  
  99.     op$ = _TRIM$(op$) 'save fixing each time
  100.     ca$ = _TRIM$(a$): cb$ = _TRIM$(b$) 'make copies in case we change
  101.     'strip signs and decimals
  102.     IF LEFT$(ca$, 1) = "-" THEN
  103.         aSgn$ = "-": ca$ = MID$(ca$, 2)
  104.     ELSE
  105.         aSgn$ = "": ca$ = ca$
  106.     END IF
  107.     dp = INSTR(ca$, ".")
  108.     IF dp > 0 THEN
  109.         adp = LEN(ca$) - dp
  110.         ca$ = MID$(ca$, 1, dp - 1) + MID$(ca$, dp + 1)
  111.     ELSE
  112.         adp = 0
  113.     END IF
  114.     IF LEFT$(cb$, 1) = "-" THEN
  115.         bSgn$ = "-": cb$ = MID$(cb$, 2)
  116.     ELSE
  117.         bSgn$ = "": cb$ = cb$
  118.     END IF
  119.     dp = INSTR(cb$, ".")
  120.     IF dp > 0 THEN
  121.         bdp = LEN(cb$) - dp
  122.         cb$ = MID$(cb$, 1, dp - 1) + MID$(cb$, dp + 1)
  123.     ELSE
  124.         bdp = 0
  125.     END IF
  126.  
  127.     IF op$ = "+" OR op$ = "-" OR op$ = "/" THEN 'add or subtr  even up strings on right of decimal
  128.         'even up the right sides of decimals if any
  129.         IF adp > bdp THEN dp = adp ELSE dp = bdp
  130.         IF adp < dp THEN ca$ = ca$ + STRING$(dp - adp, "0")
  131.         IF bdp < dp THEN cb$ = cb$ + STRING$(dp - bdp, "0")
  132.     ELSEIF op$ = "*" THEN
  133.         dp = adp + bdp
  134.     END IF
  135.     IF op$ = "*" OR op$ = "/" THEN
  136.         IF aSgn$ = bSgn$ THEN sgn$ = "" ELSE sgn$ = "-"
  137.     END IF
  138.     'now according to signs and op$ call add$ or subtr$
  139.     IF op$ = "+" THEN
  140.         IF aSgn$ = bSgn$ THEN 'really add
  141.             postOp$ = aSgn$ + add$(ca$, cb$)
  142.         ELSE 'have a case of subtraction
  143.             IF aSgn$ = "-" THEN postOp$ = subtr$(cb$, ca$) ELSE postOp$ = subtr$(ca$, cb$)
  144.         END IF
  145.     ELSEIF op$ = "-" THEN
  146.         IF bSgn$ = "-" THEN 'really add but switch b sign
  147.             bSgn$ = ""
  148.             IF aSgn$ = "-" THEN
  149.                 postOp$ = subtr$(cb$, ca$)
  150.             ELSE 'aSgn = ""
  151.                 postOp$ = add$(ca$, cb$)
  152.             END IF
  153.         ELSE 'bSgn$ =""
  154.             IF aSgn$ = "-" THEN
  155.                 bSgn$ = "-"
  156.                 postOp$ = aSgn$ + add$(ca$, cb$)
  157.             ELSE
  158.                 postOp$ = subtr$(ca$, cb$)
  159.             END IF
  160.         END IF
  161.     ELSEIF op$ = "*" THEN
  162.         postOp$ = sgn$ + mult$(ca$, cb$)
  163.     ELSEIF op$ = "/" THEN
  164.         postOp$ = sgn$ + divide$(ca$, cb$)
  165.     END IF ' which op
  166.     'put dp back
  167.     IF op$ <> "/" THEN
  168.         lpop = LEN(postOp$) ' put decimal back
  169.         postOp$ = MID$(postOp$, 1, lpop - dp) + "." + MID$(postOp$, lpop - dp + 1)
  170.     END IF
  171.     mr$ = trim0$(postOp$)
  172.  
  173. FUNCTION divide$ (n$, d$)
  174.     DIM di$, ndi$, nD AS INTEGER
  175.     IF trim0$(n$) = "0" THEN divide$ = "0": EXIT FUNCTION
  176.     IF trim0$(d$) = "0" THEN divide$ = "div 0": EXIT FUNCTION
  177.     IF trim0$(d$) = "1" THEN divide$ = trim0$(n$): EXIT FUNCTION '8/17 add trim0$
  178.     di$ = MID$(nInverse$(d$, 100), 2) 'chop off decimal point after
  179.     nD = LEN(di$)
  180.     ndi$ = mult$(n$, di$)
  181.     ndi$ = MID$(ndi$, 1, LEN(ndi$) - nD) + "." + RIGHT$(STRING$(nD, "0") + RIGHT$(ndi$, nD), nD)
  182.     divide$ = trim0$(ndi$)
  183.  
  184. FUNCTION nInverse$ (n$, DP AS INTEGER) 'assume decimal at very start of the string of digits returned, no rounding
  185.     DIM m$(1 TO 9), si$, r$, outstr$, d$
  186.     DIM i AS INTEGER
  187.     FOR i = 1 TO 9
  188.         si$ = _TRIM$(STR$(i))
  189.         m$(i) = mult$(si$, n$)
  190.     NEXT
  191.     outstr$ = ""
  192.     IF n$ = "0" THEN nInverse$ = "Div 0": EXIT FUNCTION
  193.     IF n$ = "1" THEN nInverse$ = "1": EXIT FUNCTION
  194.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  195.     r$ = "10"
  196.     DO
  197.         WHILE LEFT$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  198.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  199.             IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION 'check if we've reached DP length
  200.             r$ = r$ + "0"
  201.         WEND
  202.         FOR i = 9 TO 1 STEP -1
  203.             IF LTE(m$(i), r$) THEN d$ = _TRIM$(STR$(i)): EXIT FOR
  204.         NEXT
  205.         outstr$ = outstr$ + d$
  206.         IF LEN(outstr$) = DP THEN nInverse$ = outstr$: EXIT FUNCTION
  207.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  208.         IF r$ = "0" THEN nInverse$ = outstr$: EXIT FUNCTION 'found a perfect divisor
  209.         r$ = r$ + "0" 'add another place
  210.     LOOP
  211.  
  212. FUNCTION mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  213.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER, dp AS INTEGER
  214.     DIM f18$, f1$, t$, build$, accum$
  215.  
  216.     IF trim0$(a$) = "0" THEN mult$ = "0": EXIT FUNCTION
  217.     IF trim0$(b$) = "0" THEN mult$ = "0": EXIT FUNCTION
  218.     IF trim0$(a$) = "1" THEN mult$ = trim0$(b$): EXIT FUNCTION
  219.     IF trim0$(b$) = "1" THEN mult$ = trim0$(a$): EXIT FUNCTION
  220.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  221.     la = LEN(a$): lb = LEN(b$)
  222.     IF la > lb THEN
  223.         m = INT(la / 18) + 1
  224.         f18$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  225.         f1$ = b$
  226.     ELSE
  227.         m = INT(lb / 18) + 1
  228.         f18$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  229.         f1$ = a$
  230.     END IF
  231.     FOR dp = LEN(f1$) TO 1 STEP -1 'dp = digit position of the f1$
  232.         build$ = "" 'line builder
  233.         co = 0
  234.         'now taking 18 digits at a time Thanks Steve McNeill
  235.         FOR g = 1 TO m
  236.             v18 = VAL(MID$(f18$, m * 18 - g * 18 + 1, 18))
  237.             sd = VAL(MID$(f1$, dp, 1))
  238.             t$ = RIGHT$(STRING$(19, "0") + _TRIM$(STR$(v18 * sd + co)), 19)
  239.             co = VAL(MID$(t$, 1, 1))
  240.             build$ = MID$(t$, 2) + build$
  241.         NEXT g
  242.         IF co THEN build$ = _TRIM$(STR$(co)) + build$
  243.         IF dp = LEN(f1$) THEN
  244.             accum$ = build$
  245.         ELSE
  246.             accum$ = add$(accum$, build$ + STRING$(LEN(f1$) - dp, "0"))
  247.         END IF
  248.     NEXT dp
  249.     mult$ = accum$
  250.  
  251. FUNCTION subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  252.     DIM m AS INTEGER, g AS INTEGER, p AS INTEGER
  253.     DIM ts$, tm$, sign$, LG$, sm$, t$, result$
  254.  
  255.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  256.     IF trim0(ts$) = trim0$(tm$) THEN subtr$ = "0": EXIT FUNCTION 'OK proceed with function knowing they are not equal
  257.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  258.     IF LTE(ts$, tm$) THEN ' which is bigger? minus is bigger
  259.         sign$ = "-"
  260.         m = INT(LEN(tm$) / 18) + 1
  261.         LG$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  262.         sm$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  263.     ELSE 'sum is bigger
  264.         sign$ = ""
  265.         m = INT(LEN(ts$) / 18) + 1
  266.         LG$ = RIGHT$(STRING$(m * 18, "0") + ts$, m * 18)
  267.         sm$ = RIGHT$(STRING$(m * 18, "0") + tm$, m * 18)
  268.     END IF
  269.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  270.     FOR g = 1 TO m
  271.         VB = VAL(MID$(LG$, m * 18 - g * 18 + 1, 18))
  272.         vs = VAL(MID$(sm$, m * 18 - g * 18 + 1, 18))
  273.         IF vs > VB THEN
  274.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(tenE18 - vs + VB)), 18)
  275.             p = (m - g) * 18
  276.             WHILE p > 0 AND MID$(LG$, p, 1) = "0"
  277.                 MID$(LG$, p, 1) = "9"
  278.                 p = p - 1
  279.             WEND
  280.             IF p > 0 THEN MID$(LG$, p, 1) = _TRIM$(STR$(VAL(MID$(LG$, p, 1)) - 1))
  281.         ELSE
  282.             t$ = RIGHT$(STRING$(18, "0") + _TRIM$(STR$(VB - vs)), 18)
  283.         END IF
  284.         result$ = t$ + result$
  285.     NEXT
  286.     subtr$ = sign$ + result$
  287.  
  288. FUNCTION add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  289.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  290.     DIM la AS INTEGER, lb AS INTEGER, m AS INTEGER, g AS INTEGER
  291.     DIM fa$, fb$, t$, new$, result$
  292.     la = LEN(a$): lb = LEN(b$)
  293.     IF la > lb THEN m = INT(la / 18) + 1 ELSE m = INT(lb / 18) + 1
  294.     fa$ = RIGHT$(STRING$(m * 18, "0") + a$, m * 18)
  295.     fb$ = RIGHT$(STRING$(m * 18, "0") + b$, m * 18)
  296.  
  297.     'now taking 18 digits at a time Thanks Steve McNeill
  298.     FOR g = 1 TO m
  299.         sa = VAL(MID$(fa$, m * 18 - g * 18 + 1, 18))
  300.         sb = VAL(MID$(fb$, m * 18 - g * 18 + 1, 18))
  301.         t$ = RIGHT$(STRING$(36, "0") + _TRIM$(STR$(sa + sb + co)), 36)
  302.         co = VAL(MID$(t$, 1, 18))
  303.         new$ = MID$(t$, 19)
  304.         result$ = new$ + result$
  305.     NEXT
  306.     IF co THEN result$ = STR$(co) + result$
  307.     add$ = result$
  308.  
  309. ' String Math Helpers -----------------------------------------------
  310.  
  311. 'this function needs TrimLead0$(s$)
  312. FUNCTION LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings
  313.     DIM ca$, cb$, la AS INTEGER, lb AS INTEGER, i AS INTEGER
  314.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  315.     la = LEN(ca$): lb = LEN(cb$)
  316.     IF ca$ = cb$ THEN
  317.         LTE = -1
  318.     ELSEIF la < lb THEN ' a is smaller
  319.         LTE = -1
  320.     ELSEIF la > lb THEN ' a is bigger
  321.         LTE = 0
  322.     ELSEIF la = lb THEN ' equal lengths
  323.         FOR i = 1 TO LEN(ca$)
  324.             IF VAL(MID$(ca$, i, 1)) > VAL(MID$(cb$, i, 1)) THEN
  325.                 LTE = 0: EXIT FUNCTION
  326.             ELSEIF VAL(MID$(ca$, i, 1)) < VAL(MID$(cb$, i, 1)) THEN
  327.                 LTE = -1: EXIT FUNCTION
  328.             END IF
  329.         NEXT
  330.     END IF
  331.  
  332. ' ------------------------------------- use these for final display
  333.  
  334. FUNCTION TrimLead0$ (s$) 'for treating strings as number (pos integers)
  335.     DIM copys$, i AS INTEGER, find AS INTEGER
  336.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  337.     i = 1: find = 0
  338.     WHILE i < LEN(copys$) AND MID$(copys$, i, 1) = "0"
  339.         i = i + 1: find = 1
  340.     WEND
  341.     IF find = 1 THEN copys$ = MID$(copys$, i)
  342.     IF copys$ = "" THEN TrimLead0$ = "0" ELSE TrimLead0$ = copys$
  343.  
  344. FUNCTION TrimTail0$ (s$)
  345.     DIM copys$, dp AS INTEGER, i AS INTEGER, find AS INTEGER
  346.     copys$ = _TRIM$(s$) 'might as well remove spaces too
  347.     TrimTail0$ = copys$
  348.     dp = INSTR(copys$, ".")
  349.     IF dp > 0 THEN
  350.         i = LEN(copys$): find = 0
  351.         WHILE i > dp AND MID$(copys$, i, 1) = "0"
  352.             i = i - 1: find = 1
  353.         WEND
  354.         IF find = 1 THEN
  355.             IF i = dp THEN
  356.                 TrimTail0$ = MID$(copys$, 1, dp - 1)
  357.             ELSE
  358.                 TrimTail0$ = MID$(copys$, 1, i)
  359.             END IF
  360.         END IF
  361.     END IF
  362.  
  363. FUNCTION trim0$ (s$)
  364.     DIM cs$, si$
  365.     cs$ = s$
  366.     si$ = LEFT$(cs$, 1)
  367.     IF si$ = "-" THEN cs$ = MID$(cs$, 2)
  368.     cs$ = TrimLead0$(cs$)
  369.     cs$ = TrimTail0$(cs$)
  370.     IF RIGHT$(cs$, 1) = "." THEN cs$ = MID$(cs$, 1, LEN(cs$) - 1)
  371.     IF si$ = "-" THEN trim0$ = si$ + cs$ ELSE trim0$ = cs$
  372.  
  373. ' for displaying truncated numbers say to 60 digits
  374. FUNCTION showDP$ (num$, nDP AS INTEGER)
  375.     DIM cNum$, dp AS INTEGER, d AS INTEGER, i AS INTEGER
  376.     cNum$ = num$ 'since num$ could get changed
  377.     showDP$ = num$
  378.     dp = INSTR(num$, ".")
  379.     IF dp > 0 THEN
  380.         IF LEN(MID$(cNum$, dp + 1)) > nDP THEN
  381.             d = VAL(MID$(cNum$, dp + nDP + 1, 1))
  382.             IF d > 4 THEN
  383.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  384.                 dp = dp + 1
  385.                 i = dp + nDP
  386.                 WHILE MID$(cNum$, i, 1) = "9" OR MID$(cNum$, i, 1) = "."
  387.                     IF MID$(cNum$, i, 1) = "9" THEN
  388.                         MID$(cNum$, i, 1) = "0"
  389.                     END IF
  390.                     i = i - 1
  391.                 WEND
  392.                 MID$(cNum$, i, 1) = _TRIM$(STR$(VAL(MID$(cNum$, i, 1)) + 1)) 'last non 9 digit
  393.                 cNum$ = MID$(cNum$, 1, dp + nDP) 'chop it
  394.                 showDP$ = trim0$(cNum$)
  395.             ELSE
  396.                 showDP$ = MID$(cNum$, 1, dp + nDP)
  397.             END IF
  398.         END IF
  399.     END IF
  400.  


Update 2021-06-03: Nope! Sorry the divide$ function was buggy. Had to pull the link to this code from Library.
« Last Edit: June 03, 2021, 09:41:04 am by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #26 on: June 03, 2021, 09:24:02 am »
Attempting to extend String Math into Square Roots, I discovered a huge bug with Divide$ Function, it's all commented in code below. I think I have it patched but maybe not. This is tricky without Boolean Compares for String Math.

Main code is testing a 100 digit precision Square Root Code and Divide$ has the "fixed"? String Math patch
Code: QB64: [Select]
  1. _Title "Math regulator mr test sqr estimating" ' b+ start 2021-06-02
  2. Randomize Timer 'now that it's seems to be running silent
  3. Screen _NewImage(1200, 700, 32)
  4. _Delay .25
  5.  
  6. Dim n$, guess$, other$, cont$, sum$, loopCnt, lastGuess$
  7.     'remember everything is strings
  8.     Input "Enter a number to find it's square root "; n$
  9.     If n$ = "" Then End
  10.     guess$ = mr$(n$, "/", "2") ' for n$ = 2
  11.     other$ = n$
  12.     loopCnt = 0
  13.     Do
  14.         loopCnt = loopCnt + 1
  15.         Print loopCnt; " guess: "; guess$
  16.         'Input "Continue press enter, any other to quit "; cont$
  17.         'If cont$ = "" Then
  18.         If (Mid$(guess$, 1, 105) = Mid$(lastGuess$, 1, 105)) Then ' go past 100 matching digits for 100 digit precision
  19.             Print
  20.             Print "100 digits precision goal, not 100 digits past decimal: Square root of "; n$; " is:"
  21.             Print Mid$(guess$, 1, 101) ' one char for decimal
  22.             _Clipboard$ = Mid$(guess$, 1, 101) ' ditto
  23.             Print
  24.             Exit Do
  25.         Else
  26.             lastGuess$ = guess$
  27.             'Print "guess$ "; guess$
  28.             'Print "other$ "; other$
  29.             sum$ = mr$(guess$, "+", other$)
  30.             'Print "sum$ "; sum$
  31.             'Print "Call divide for guess$, sum$ / 2"
  32.             guess$ = mr$(sum$, "/", "2")
  33.             Print "New guess$ "; Mid$(guess$, 1, 105)
  34.             'Print "Call n$ divide  new guess$ for other$, n$ / guess$"
  35.             other$ = mr$(n$, "/", guess$)
  36.             Print "New other$ "; Mid$(other$, 1, 105)
  37.         End If
  38.         'Else
  39.         '    Exit Do
  40.         'End If
  41.     Loop
  42.  
  43. Function mr$ (a$, op$, b$) ' catchy? mr$ for math regulator
  44.     Dim ca$, cb$, aSgn$, bSgn$, postOp$, sgn$
  45.     Dim adp As Integer, bdp As Integer, dp As Integer, lpop As Integer
  46.  
  47.     op$ = _Trim$(op$) 'save fixing each time
  48.     ca$ = _Trim$(a$): cb$ = _Trim$(b$) 'make copies in case we change
  49.     'strip signs and decimals
  50.     If Left$(ca$, 1) = "-" Then
  51.         aSgn$ = "-": ca$ = Mid$(ca$, 2)
  52.     Else
  53.         aSgn$ = "": ca$ = ca$
  54.     End If
  55.     dp = InStr(ca$, ".")
  56.     If dp > 0 Then
  57.         adp = Len(ca$) - dp
  58.         ca$ = Mid$(ca$, 1, dp - 1) + Mid$(ca$, dp + 1)
  59.     Else
  60.         adp = 0
  61.     End If
  62.     If Left$(cb$, 1) = "-" Then
  63.         bSgn$ = "-": cb$ = Mid$(cb$, 2)
  64.     Else
  65.         bSgn$ = "": cb$ = cb$
  66.     End If
  67.     dp = InStr(cb$, ".")
  68.     If dp > 0 Then
  69.         bdp = Len(cb$) - dp
  70.         cb$ = Mid$(cb$, 1, dp - 1) + Mid$(cb$, dp + 1)
  71.     Else
  72.         bdp = 0
  73.     End If
  74.  
  75.     If op$ = "+" Or op$ = "-" Or op$ = "/" Then 'add or subtr  even up strings on right of decimal
  76.         'even up the right sides of decimals if any
  77.         If adp > bdp Then dp = adp Else dp = bdp
  78.         If adp < dp Then ca$ = ca$ + String$(dp - adp, "0")
  79.         If bdp < dp Then cb$ = cb$ + String$(dp - bdp, "0")
  80.     ElseIf op$ = "*" Then
  81.         dp = adp + bdp
  82.     End If
  83.     If op$ = "*" Or op$ = "/" Then
  84.         If aSgn$ = bSgn$ Then sgn$ = "" Else sgn$ = "-"
  85.     End If
  86.     'now according to signs and op$ call add$ or subtr$
  87.     If op$ = "+" Then
  88.         If aSgn$ = bSgn$ Then 'really add
  89.             postOp$ = aSgn$ + add$(ca$, cb$)
  90.         Else 'have a case of subtraction
  91.             If aSgn$ = "-" Then postOp$ = subtr$(cb$, ca$) Else postOp$ = subtr$(ca$, cb$)
  92.         End If
  93.     ElseIf op$ = "-" Then
  94.         If bSgn$ = "-" Then 'really add but switch b sign
  95.             bSgn$ = ""
  96.             If aSgn$ = "-" Then
  97.                 postOp$ = subtr$(cb$, ca$)
  98.             Else 'aSgn = ""
  99.                 postOp$ = add$(ca$, cb$)
  100.             End If
  101.         Else 'bSgn$ =""
  102.             If aSgn$ = "-" Then
  103.                 bSgn$ = "-"
  104.                 postOp$ = aSgn$ + add$(ca$, cb$)
  105.             Else
  106.                 postOp$ = subtr$(ca$, cb$)
  107.             End If
  108.         End If
  109.     ElseIf op$ = "*" Then
  110.         postOp$ = sgn$ + mult$(ca$, cb$)
  111.     ElseIf op$ = "/" Then
  112.         postOp$ = sgn$ + divide$(ca$, cb$)
  113.     End If ' which op
  114.     'put dp back
  115.     If op$ <> "/" Then
  116.         lpop = Len(postOp$) ' put decimal back
  117.         postOp$ = Mid$(postOp$, 1, lpop - dp) + "." + Mid$(postOp$, lpop - dp + 1)
  118.     End If
  119.     mr$ = trim0$(postOp$)
  120.  
  121. Function divide$ (n$, d$) ' goal here is 100 digits precision not 100 digits past decimal
  122.     'Print "divide$ got n$ = "; n$; " and d$ = "; d$
  123.     Dim di$, ndi$, nD As Integer
  124.     If trim0$(n$) = "0" Then divide$ = "0": Exit Function
  125.     If trim0$(d$) = "0" Then divide$ = "div 0": Exit Function
  126.     If trim0$(d$) = "1" Then divide$ = trim0$(n$): Exit Function '8/17 add trim0$
  127.  
  128.     ' aha! found a bug when d$ gets really huge 100 is no where near enough!!!!
  129.     ' 2021-06-03 fix by adding 100 to len(d$), plus have to go a little past 100 for 100 digit precision
  130.     ' need to go past 100 for 100 precise digits (not decimal places)
  131.     di$ = Mid$(nInverse$(d$, Len(d$) + 200), 2) 'chop off decimal point after
  132.     'Print "divide$ inverse of d$ = di$ = "; di$
  133.  
  134.     nD = Len(di$)
  135.     ndi$ = mult$(n$, di$)
  136.     ndi$ = Mid$(ndi$, 1, Len(ndi$) - nD) + "." + Right$(String$(nD, "0") + Right$(ndi$, nD), nD)
  137.     divide$ = trim0$(ndi$)
  138.  
  139. Function nInverse$ (n$, DP As Integer) 'assume decimal at very start of the string of digits returned, no rounding
  140.     Dim m$(1 To 9), si$, r$, outstr$, d$
  141.     Dim i As Integer
  142.     For i = 1 To 9
  143.         si$ = _Trim$(Str$(i))
  144.         m$(i) = mult$(si$, n$)
  145.     Next
  146.     outstr$ = ""
  147.     If n$ = "0" Then nInverse$ = "Div 0": Exit Function
  148.     If n$ = "1" Then nInverse$ = "1": Exit Function
  149.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  150.     r$ = "10"
  151.     Do
  152.         While Left$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  153.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  154.             If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function 'check if we've reached DP length
  155.             r$ = r$ + "0"
  156.         Wend
  157.         For i = 9 To 1 Step -1
  158.             If LTE(m$(i), r$) Then d$ = _Trim$(Str$(i)): Exit For
  159.         Next
  160.         outstr$ = outstr$ + d$
  161.         If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function
  162.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  163.         If r$ = "0" Then nInverse$ = outstr$: Exit Function 'found a perfect divisor
  164.         r$ = r$ + "0" 'add another place
  165.     Loop
  166.  
  167. Function mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  168.     Dim la As Integer, lb As Integer, m As Integer, g As Integer, dp As Integer
  169.     Dim f18$, f1$, t$, build$, accum$
  170.  
  171.     If trim0$(a$) = "0" Then mult$ = "0": Exit Function
  172.     If trim0$(b$) = "0" Then mult$ = "0": Exit Function
  173.     If trim0$(a$) = "1" Then mult$ = trim0$(b$): Exit Function
  174.     If trim0$(b$) = "1" Then mult$ = trim0$(a$): Exit Function
  175.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  176.     la = Len(a$): lb = Len(b$)
  177.     If la > lb Then
  178.         m = Int(la / 18) + 1
  179.         f18$ = Right$(String$(m * 18, "0") + a$, m * 18)
  180.         f1$ = b$
  181.     Else
  182.         m = Int(lb / 18) + 1
  183.         f18$ = Right$(String$(m * 18, "0") + b$, m * 18)
  184.         f1$ = a$
  185.     End If
  186.     For dp = Len(f1$) To 1 Step -1 'dp = digit position of the f1$
  187.         build$ = "" 'line builder
  188.         co = 0
  189.         'now taking 18 digits at a time Thanks Steve McNeill
  190.         For g = 1 To m
  191.             v18 = Val(Mid$(f18$, m * 18 - g * 18 + 1, 18))
  192.             sd = Val(Mid$(f1$, dp, 1))
  193.             t$ = Right$(String$(19, "0") + _Trim$(Str$(v18 * sd + co)), 19)
  194.             co = Val(Mid$(t$, 1, 1))
  195.             build$ = Mid$(t$, 2) + build$
  196.         Next g
  197.         If co Then build$ = _Trim$(Str$(co)) + build$
  198.         If dp = Len(f1$) Then
  199.             accum$ = build$
  200.         Else
  201.             accum$ = add$(accum$, build$ + String$(Len(f1$) - dp, "0"))
  202.         End If
  203.     Next dp
  204.     mult$ = accum$
  205.  
  206. Function subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  207.     Dim m As Integer, g As Integer, p As Integer
  208.     Dim ts$, tm$, sign$, LG$, sm$, t$, result$
  209.  
  210.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  211.     If trim0(ts$) = trim0$(tm$) Then subtr$ = "0": Exit Function 'OK proceed with function knowing they are not equal
  212.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  213.     If LTE(ts$, tm$) Then ' which is bigger? minus is bigger
  214.         sign$ = "-"
  215.         m = Int(Len(tm$) / 18) + 1
  216.         LG$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  217.         sm$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  218.     Else 'sum is bigger
  219.         sign$ = ""
  220.         m = Int(Len(ts$) / 18) + 1
  221.         LG$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  222.         sm$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  223.     End If
  224.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  225.     For g = 1 To m
  226.         VB = Val(Mid$(LG$, m * 18 - g * 18 + 1, 18))
  227.         vs = Val(Mid$(sm$, m * 18 - g * 18 + 1, 18))
  228.         If vs > VB Then
  229.             t$ = Right$(String$(18, "0") + _Trim$(Str$(tenE18 - vs + VB)), 18)
  230.             p = (m - g) * 18
  231.             While p > 0 And Mid$(LG$, p, 1) = "0"
  232.                 Mid$(LG$, p, 1) = "9"
  233.                 p = p - 1
  234.             Wend
  235.             If p > 0 Then Mid$(LG$, p, 1) = _Trim$(Str$(Val(Mid$(LG$, p, 1)) - 1))
  236.         Else
  237.             t$ = Right$(String$(18, "0") + _Trim$(Str$(VB - vs)), 18)
  238.         End If
  239.         result$ = t$ + result$
  240.     Next
  241.     subtr$ = sign$ + result$
  242.  
  243. Function add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  244.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  245.     Dim la As Integer, lb As Integer, m As Integer, g As Integer
  246.     Dim fa$, fb$, t$, new$, result$
  247.     la = Len(a$): lb = Len(b$)
  248.     If la > lb Then m = Int(la / 18) + 1 Else m = Int(lb / 18) + 1
  249.     fa$ = Right$(String$(m * 18, "0") + a$, m * 18)
  250.     fb$ = Right$(String$(m * 18, "0") + b$, m * 18)
  251.  
  252.     'now taking 18 digits at a time Thanks Steve McNeill
  253.     For g = 1 To m
  254.         sa = Val(Mid$(fa$, m * 18 - g * 18 + 1, 18))
  255.         sb = Val(Mid$(fb$, m * 18 - g * 18 + 1, 18))
  256.         t$ = Right$(String$(36, "0") + _Trim$(Str$(sa + sb + co)), 36)
  257.         co = Val(Mid$(t$, 1, 18))
  258.         new$ = Mid$(t$, 19)
  259.         result$ = new$ + result$
  260.     Next
  261.     If co Then result$ = Str$(co) + result$
  262.     add$ = result$
  263.  
  264. ' String Math Helpers -----------------------------------------------
  265.  
  266. 'this function needs TrimLead0$(s$)
  267. Function LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings, For Integers Only!
  268.     Dim ca$, cb$, la As Integer, lb As Integer, i As Integer
  269.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  270.     la = Len(ca$): lb = Len(cb$)
  271.     If ca$ = cb$ Then
  272.         LTE = -1
  273.     ElseIf la < lb Then ' a is smaller
  274.         LTE = -1
  275.     ElseIf la > lb Then ' a is bigger
  276.         LTE = 0
  277.     ElseIf la = lb Then ' equal lengths
  278.         For i = 1 To Len(ca$)
  279.             If Val(Mid$(ca$, i, 1)) > Val(Mid$(cb$, i, 1)) Then
  280.                 LTE = 0: Exit Function
  281.             ElseIf Val(Mid$(ca$, i, 1)) < Val(Mid$(cb$, i, 1)) Then
  282.                 LTE = -1: Exit Function
  283.             End If
  284.         Next
  285.     End If
  286.  
  287. ' ------------------------------------- use these for final display
  288.  
  289. Function TrimLead0$ (s$) 'for treating strings as number (pos integers)
  290.     Dim copys$, i As Integer, find As Integer
  291.     copys$ = _Trim$(s$) 'might as well remove spaces too
  292.     i = 1: find = 0
  293.     While i < Len(copys$) And Mid$(copys$, i, 1) = "0"
  294.         i = i + 1: find = 1
  295.     Wend
  296.     If find = 1 Then copys$ = Mid$(copys$, i)
  297.     If copys$ = "" Then TrimLead0$ = "0" Else TrimLead0$ = copys$
  298.  
  299. Function TrimTail0$ (s$)
  300.     Dim copys$, dp As Integer, i As Integer, find As Integer
  301.     copys$ = _Trim$(s$) 'might as well remove spaces too
  302.     TrimTail0$ = copys$
  303.     dp = InStr(copys$, ".")
  304.     If dp > 0 Then
  305.         i = Len(copys$): find = 0
  306.         While i > dp And Mid$(copys$, i, 1) = "0"
  307.             i = i - 1: find = 1
  308.         Wend
  309.         If find = 1 Then
  310.             If i = dp Then
  311.                 TrimTail0$ = Mid$(copys$, 1, dp - 1)
  312.             Else
  313.                 TrimTail0$ = Mid$(copys$, 1, i)
  314.             End If
  315.         End If
  316.     End If
  317.  
  318. Function trim0$ (s$)
  319.     Dim cs$, si$
  320.     cs$ = s$
  321.     si$ = Left$(cs$, 1)
  322.     If si$ = "-" Then cs$ = Mid$(cs$, 2)
  323.     cs$ = TrimLead0$(cs$)
  324.     cs$ = TrimTail0$(cs$)
  325.     If Right$(cs$, 1) = "." Then cs$ = Mid$(cs$, 1, Len(cs$) - 1)
  326.     If si$ = "-" Then trim0$ = si$ + cs$ Else trim0$ = cs$
  327.  
  328. ' for displaying truncated numbers say to 60 digits
  329. Function showDP$ (num$, nDP As Integer)
  330.     Dim cNum$, dp As Integer, d As Integer, i As Integer
  331.     cNum$ = num$ 'since num$ could get changed
  332.     showDP$ = num$
  333.     dp = InStr(num$, ".")
  334.     If dp > 0 Then
  335.         If Len(Mid$(cNum$, dp + 1)) > nDP Then
  336.             d = Val(Mid$(cNum$, dp + nDP + 1, 1))
  337.             If d > 4 Then
  338.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  339.                 dp = dp + 1
  340.                 i = dp + nDP
  341.                 While Mid$(cNum$, i, 1) = "9" Or Mid$(cNum$, i, 1) = "."
  342.                     If Mid$(cNum$, i, 1) = "9" Then
  343.                         Mid$(cNum$, i, 1) = "0"
  344.                     End If
  345.                     i = i - 1
  346.                 Wend
  347.                 Mid$(cNum$, i, 1) = _Trim$(Str$(Val(Mid$(cNum$, i, 1)) + 1)) 'last non 9 digit
  348.                 cNum$ = Mid$(cNum$, 1, dp + nDP) 'chop it
  349.                 showDP$ = trim0$(cNum$)
  350.             Else
  351.                 showDP$ = Mid$(cNum$, 1, dp + nDP)
  352.             End If
  353.         End If
  354.     End If
  355.  

Going back to oh to replace the Divide$ function there.

Here is a list of compares between Square Root from above code and an Internet Calculator (that rounds it's last digit in decimal place, whereas this code cuts off at that digit # not decimal place.)
Code: [Select]
First show code clipboard result then show Internet Calculator result
from https://www.mathsisfun.com/calculator-precision.html

Square root 2 first from String Math then Internet Result 
1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727

Square root of 3...
1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575
1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485757

Square root of 4...
gets stuck in infinite loop 1.999999999999999999999999....   
2 of course!

OK compare guess to last guess and if they match out to 105 digits then 100 digits must be accurate
1.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
2 of course!

SR 5
2.236067977499789696409173668731276235440618359611525724270897245410520925637804899414414408378782274
2.23606797749978969640917366873127623544061835961152572427089724541052092563780489941441440837878227497

SR 6
2.449489742783178098197284074705891391965947480656670128432692567250960377457315026539859433104640234
2.44948974278317809819728407470589139196594748065667012843269256725096037745731502653985943310464023482

SR 7
2.645751311064590590501615753639260425710259183082450180368334459201068823230283627760392886474543610
2.64575131106459059050161575363926042571025918308245018036833445920106882323028362776039288647454361062

« Last Edit: June 03, 2021, 09:46:38 am by bplus »

Offline George McGinn

  • Global Moderator
  • Forum Regular
  • Posts: 210
    • Resume
Re: String Math
« Reply #27 on: June 03, 2021, 03:15:45 pm »
I will post it here again, but my square root function gives you the correct answer.

I use the  The Newton-Raphson Iteration method to calculate the square root as well as the n'th roots (see link: https://secure.math.ubc.ca/~anstee/math104/104newtonmethod.pdf).

It is more accurate than your "best answer."

Here is the code to execute this method:

Code: QB64: [Select]
  1.  
  2. DECLARE FUNCTION root_2 (A)
  3. DECLARE FUNCTION root_n (A, n)
  4.  
  5. '*** square root function
  6. A = 4503599627370496: r = root_2(A): PRINT "square root of "; A; " equals "; result
  7. A = 125: r = root_2(A): PRINT "square root of "; A; " equals "; result
  8. A = 100: r = root_2(A): PRINT "square root of "; A; " equals "; result
  9.  
  10.  
  11. FUNCTION root_2 (A AS DOUBLE)
  12.     x = A / 2: precision = 0.000001: max_iterations = 30
  13.     FOR i = 1 TO max_iterations
  14.         dx = (x - A / x) / 2: x = x - dx
  15.         IF ABS(dx) < precision THEN i = max_iterations
  16.     NEXT i
  17.     result = STR$(x)




Attempting to extend String Math into Square Roots, I discovered a huge bug with Divide$ Function, it's all commented in code below. I think I have it patched but maybe not. This is tricky without Boolean Compares for String Math.

Main code is testing a 100 digit precision Square Root Code and Divide$ has the "fixed"? String Math patch
Code: QB64: [Select]
  1. _Title "Math regulator mr test sqr estimating" ' b+ start 2021-06-02
  2. Randomize Timer 'now that it's seems to be running silent
  3. Screen _NewImage(1200, 700, 32)
  4. _Delay .25
  5.  
  6. Dim n$, guess$, other$, cont$, sum$, loopCnt, lastGuess$
  7.     'remember everything is strings
  8.     Input "Enter a number to find it's square root "; n$
  9.     If n$ = "" Then End
  10.     guess$ = mr$(n$, "/", "2") ' for n$ = 2
  11.     other$ = n$
  12.     loopCnt = 0
  13.     Do
  14.         loopCnt = loopCnt + 1
  15.         Print loopCnt; " guess: "; guess$
  16.         'Input "Continue press enter, any other to quit "; cont$
  17.         'If cont$ = "" Then
  18.         If (Mid$(guess$, 1, 105) = Mid$(lastGuess$, 1, 105)) Then ' go past 100 matching digits for 100 digit precision
  19.             Print
  20.             Print "100 digits precision goal, not 100 digits past decimal: Square root of "; n$; " is:"
  21.             Print Mid$(guess$, 1, 101) ' one char for decimal
  22.             _Clipboard$ = Mid$(guess$, 1, 101) ' ditto
  23.             Print
  24.             Exit Do
  25.         Else
  26.             lastGuess$ = guess$
  27.             'Print "guess$ "; guess$
  28.             'Print "other$ "; other$
  29.             sum$ = mr$(guess$, "+", other$)
  30.             'Print "sum$ "; sum$
  31.             'Print "Call divide for guess$, sum$ / 2"
  32.             guess$ = mr$(sum$, "/", "2")
  33.             Print "New guess$ "; Mid$(guess$, 1, 105)
  34.             'Print "Call n$ divide  new guess$ for other$, n$ / guess$"
  35.             other$ = mr$(n$, "/", guess$)
  36.             Print "New other$ "; Mid$(other$, 1, 105)
  37.         End If
  38.         'Else
  39.         '    Exit Do
  40.         'End If
  41.     Loop
  42.  
  43. Function mr$ (a$, op$, b$) ' catchy? mr$ for math regulator
  44.     Dim ca$, cb$, aSgn$, bSgn$, postOp$, sgn$
  45.     Dim adp As Integer, bdp As Integer, dp As Integer, lpop As Integer
  46.  
  47.     op$ = _Trim$(op$) 'save fixing each time
  48.     ca$ = _Trim$(a$): cb$ = _Trim$(b$) 'make copies in case we change
  49.     'strip signs and decimals
  50.     If Left$(ca$, 1) = "-" Then
  51.         aSgn$ = "-": ca$ = Mid$(ca$, 2)
  52.     Else
  53.         aSgn$ = "": ca$ = ca$
  54.     End If
  55.     dp = InStr(ca$, ".")
  56.     If dp > 0 Then
  57.         adp = Len(ca$) - dp
  58.         ca$ = Mid$(ca$, 1, dp - 1) + Mid$(ca$, dp + 1)
  59.     Else
  60.         adp = 0
  61.     End If
  62.     If Left$(cb$, 1) = "-" Then
  63.         bSgn$ = "-": cb$ = Mid$(cb$, 2)
  64.     Else
  65.         bSgn$ = "": cb$ = cb$
  66.     End If
  67.     dp = InStr(cb$, ".")
  68.     If dp > 0 Then
  69.         bdp = Len(cb$) - dp
  70.         cb$ = Mid$(cb$, 1, dp - 1) + Mid$(cb$, dp + 1)
  71.     Else
  72.         bdp = 0
  73.     End If
  74.  
  75.     If op$ = "+" Or op$ = "-" Or op$ = "/" Then 'add or subtr  even up strings on right of decimal
  76.         'even up the right sides of decimals if any
  77.         If adp > bdp Then dp = adp Else dp = bdp
  78.         If adp < dp Then ca$ = ca$ + String$(dp - adp, "0")
  79.         If bdp < dp Then cb$ = cb$ + String$(dp - bdp, "0")
  80.     ElseIf op$ = "*" Then
  81.         dp = adp + bdp
  82.     End If
  83.     If op$ = "*" Or op$ = "/" Then
  84.         If aSgn$ = bSgn$ Then sgn$ = "" Else sgn$ = "-"
  85.     End If
  86.     'now according to signs and op$ call add$ or subtr$
  87.     If op$ = "+" Then
  88.         If aSgn$ = bSgn$ Then 'really add
  89.             postOp$ = aSgn$ + add$(ca$, cb$)
  90.         Else 'have a case of subtraction
  91.             If aSgn$ = "-" Then postOp$ = subtr$(cb$, ca$) Else postOp$ = subtr$(ca$, cb$)
  92.         End If
  93.     ElseIf op$ = "-" Then
  94.         If bSgn$ = "-" Then 'really add but switch b sign
  95.             bSgn$ = ""
  96.             If aSgn$ = "-" Then
  97.                 postOp$ = subtr$(cb$, ca$)
  98.             Else 'aSgn = ""
  99.                 postOp$ = add$(ca$, cb$)
  100.             End If
  101.         Else 'bSgn$ =""
  102.             If aSgn$ = "-" Then
  103.                 bSgn$ = "-"
  104.                 postOp$ = aSgn$ + add$(ca$, cb$)
  105.             Else
  106.                 postOp$ = subtr$(ca$, cb$)
  107.             End If
  108.         End If
  109.     ElseIf op$ = "*" Then
  110.         postOp$ = sgn$ + mult$(ca$, cb$)
  111.     ElseIf op$ = "/" Then
  112.         postOp$ = sgn$ + divide$(ca$, cb$)
  113.     End If ' which op
  114.     'put dp back
  115.     If op$ <> "/" Then
  116.         lpop = Len(postOp$) ' put decimal back
  117.         postOp$ = Mid$(postOp$, 1, lpop - dp) + "." + Mid$(postOp$, lpop - dp + 1)
  118.     End If
  119.     mr$ = trim0$(postOp$)
  120.  
  121. Function divide$ (n$, d$) ' goal here is 100 digits precision not 100 digits past decimal
  122.     'Print "divide$ got n$ = "; n$; " and d$ = "; d$
  123.     Dim di$, ndi$, nD As Integer
  124.     If trim0$(n$) = "0" Then divide$ = "0": Exit Function
  125.     If trim0$(d$) = "0" Then divide$ = "div 0": Exit Function
  126.     If trim0$(d$) = "1" Then divide$ = trim0$(n$): Exit Function '8/17 add trim0$
  127.  
  128.     ' aha! found a bug when d$ gets really huge 100 is no where near enough!!!!
  129.     ' 2021-06-03 fix by adding 100 to len(d$), plus have to go a little past 100 for 100 digit precision
  130.     ' need to go past 100 for 100 precise digits (not decimal places)
  131.     di$ = Mid$(nInverse$(d$, Len(d$) + 200), 2) 'chop off decimal point after
  132.     'Print "divide$ inverse of d$ = di$ = "; di$
  133.  
  134.     nD = Len(di$)
  135.     ndi$ = mult$(n$, di$)
  136.     ndi$ = Mid$(ndi$, 1, Len(ndi$) - nD) + "." + Right$(String$(nD, "0") + Right$(ndi$, nD), nD)
  137.     divide$ = trim0$(ndi$)
  138.  
  139. Function nInverse$ (n$, DP As Integer) 'assume decimal at very start of the string of digits returned, no rounding
  140.     Dim m$(1 To 9), si$, r$, outstr$, d$
  141.     Dim i As Integer
  142.     For i = 1 To 9
  143.         si$ = _Trim$(Str$(i))
  144.         m$(i) = mult$(si$, n$)
  145.     Next
  146.     outstr$ = ""
  147.     If n$ = "0" Then nInverse$ = "Div 0": Exit Function
  148.     If n$ = "1" Then nInverse$ = "1": Exit Function
  149.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  150.     r$ = "10"
  151.     Do
  152.         While Left$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  153.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  154.             If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function 'check if we've reached DP length
  155.             r$ = r$ + "0"
  156.         Wend
  157.         For i = 9 To 1 Step -1
  158.             If LTE(m$(i), r$) Then d$ = _Trim$(Str$(i)): Exit For
  159.         Next
  160.         outstr$ = outstr$ + d$
  161.         If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function
  162.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  163.         If r$ = "0" Then nInverse$ = outstr$: Exit Function 'found a perfect divisor
  164.         r$ = r$ + "0" 'add another place
  165.     Loop
  166.  
  167. Function mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  168.     Dim la As Integer, lb As Integer, m As Integer, g As Integer, dp As Integer
  169.     Dim f18$, f1$, t$, build$, accum$
  170.  
  171.     If trim0$(a$) = "0" Then mult$ = "0": Exit Function
  172.     If trim0$(b$) = "0" Then mult$ = "0": Exit Function
  173.     If trim0$(a$) = "1" Then mult$ = trim0$(b$): Exit Function
  174.     If trim0$(b$) = "1" Then mult$ = trim0$(a$): Exit Function
  175.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  176.     la = Len(a$): lb = Len(b$)
  177.     If la > lb Then
  178.         m = Int(la / 18) + 1
  179.         f18$ = Right$(String$(m * 18, "0") + a$, m * 18)
  180.         f1$ = b$
  181.     Else
  182.         m = Int(lb / 18) + 1
  183.         f18$ = Right$(String$(m * 18, "0") + b$, m * 18)
  184.         f1$ = a$
  185.     End If
  186.     For dp = Len(f1$) To 1 Step -1 'dp = digit position of the f1$
  187.         build$ = "" 'line builder
  188.         co = 0
  189.         'now taking 18 digits at a time Thanks Steve McNeill
  190.         For g = 1 To m
  191.             v18 = Val(Mid$(f18$, m * 18 - g * 18 + 1, 18))
  192.             sd = Val(Mid$(f1$, dp, 1))
  193.             t$ = Right$(String$(19, "0") + _Trim$(Str$(v18 * sd + co)), 19)
  194.             co = Val(Mid$(t$, 1, 1))
  195.             build$ = Mid$(t$, 2) + build$
  196.         Next g
  197.         If co Then build$ = _Trim$(Str$(co)) + build$
  198.         If dp = Len(f1$) Then
  199.             accum$ = build$
  200.         Else
  201.             accum$ = add$(accum$, build$ + String$(Len(f1$) - dp, "0"))
  202.         End If
  203.     Next dp
  204.     mult$ = accum$
  205.  
  206. Function subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  207.     Dim m As Integer, g As Integer, p As Integer
  208.     Dim ts$, tm$, sign$, LG$, sm$, t$, result$
  209.  
  210.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  211.     If trim0(ts$) = trim0$(tm$) Then subtr$ = "0": Exit Function 'OK proceed with function knowing they are not equal
  212.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  213.     If LTE(ts$, tm$) Then ' which is bigger? minus is bigger
  214.         sign$ = "-"
  215.         m = Int(Len(tm$) / 18) + 1
  216.         LG$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  217.         sm$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  218.     Else 'sum is bigger
  219.         sign$ = ""
  220.         m = Int(Len(ts$) / 18) + 1
  221.         LG$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  222.         sm$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  223.     End If
  224.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  225.     For g = 1 To m
  226.         VB = Val(Mid$(LG$, m * 18 - g * 18 + 1, 18))
  227.         vs = Val(Mid$(sm$, m * 18 - g * 18 + 1, 18))
  228.         If vs > VB Then
  229.             t$ = Right$(String$(18, "0") + _Trim$(Str$(tenE18 - vs + VB)), 18)
  230.             p = (m - g) * 18
  231.             While p > 0 And Mid$(LG$, p, 1) = "0"
  232.                 Mid$(LG$, p, 1) = "9"
  233.                 p = p - 1
  234.             Wend
  235.             If p > 0 Then Mid$(LG$, p, 1) = _Trim$(Str$(Val(Mid$(LG$, p, 1)) - 1))
  236.         Else
  237.             t$ = Right$(String$(18, "0") + _Trim$(Str$(VB - vs)), 18)
  238.         End If
  239.         result$ = t$ + result$
  240.     Next
  241.     subtr$ = sign$ + result$
  242.  
  243. Function add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  244.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  245.     Dim la As Integer, lb As Integer, m As Integer, g As Integer
  246.     Dim fa$, fb$, t$, new$, result$
  247.     la = Len(a$): lb = Len(b$)
  248.     If la > lb Then m = Int(la / 18) + 1 Else m = Int(lb / 18) + 1
  249.     fa$ = Right$(String$(m * 18, "0") + a$, m * 18)
  250.     fb$ = Right$(String$(m * 18, "0") + b$, m * 18)
  251.  
  252.     'now taking 18 digits at a time Thanks Steve McNeill
  253.     For g = 1 To m
  254.         sa = Val(Mid$(fa$, m * 18 - g * 18 + 1, 18))
  255.         sb = Val(Mid$(fb$, m * 18 - g * 18 + 1, 18))
  256.         t$ = Right$(String$(36, "0") + _Trim$(Str$(sa + sb + co)), 36)
  257.         co = Val(Mid$(t$, 1, 18))
  258.         new$ = Mid$(t$, 19)
  259.         result$ = new$ + result$
  260.     Next
  261.     If co Then result$ = Str$(co) + result$
  262.     add$ = result$
  263.  
  264. ' String Math Helpers -----------------------------------------------
  265.  
  266. 'this function needs TrimLead0$(s$)
  267. Function LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings, For Integers Only!
  268.     Dim ca$, cb$, la As Integer, lb As Integer, i As Integer
  269.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  270.     la = Len(ca$): lb = Len(cb$)
  271.     If ca$ = cb$ Then
  272.         LTE = -1
  273.     ElseIf la < lb Then ' a is smaller
  274.         LTE = -1
  275.     ElseIf la > lb Then ' a is bigger
  276.         LTE = 0
  277.     ElseIf la = lb Then ' equal lengths
  278.         For i = 1 To Len(ca$)
  279.             If Val(Mid$(ca$, i, 1)) > Val(Mid$(cb$, i, 1)) Then
  280.                 LTE = 0: Exit Function
  281.             ElseIf Val(Mid$(ca$, i, 1)) < Val(Mid$(cb$, i, 1)) Then
  282.                 LTE = -1: Exit Function
  283.             End If
  284.         Next
  285.     End If
  286.  
  287. ' ------------------------------------- use these for final display
  288.  
  289. Function TrimLead0$ (s$) 'for treating strings as number (pos integers)
  290.     Dim copys$, i As Integer, find As Integer
  291.     copys$ = _Trim$(s$) 'might as well remove spaces too
  292.     i = 1: find = 0
  293.     While i < Len(copys$) And Mid$(copys$, i, 1) = "0"
  294.         i = i + 1: find = 1
  295.     Wend
  296.     If find = 1 Then copys$ = Mid$(copys$, i)
  297.     If copys$ = "" Then TrimLead0$ = "0" Else TrimLead0$ = copys$
  298.  
  299. Function TrimTail0$ (s$)
  300.     Dim copys$, dp As Integer, i As Integer, find As Integer
  301.     copys$ = _Trim$(s$) 'might as well remove spaces too
  302.     TrimTail0$ = copys$
  303.     dp = InStr(copys$, ".")
  304.     If dp > 0 Then
  305.         i = Len(copys$): find = 0
  306.         While i > dp And Mid$(copys$, i, 1) = "0"
  307.             i = i - 1: find = 1
  308.         Wend
  309.         If find = 1 Then
  310.             If i = dp Then
  311.                 TrimTail0$ = Mid$(copys$, 1, dp - 1)
  312.             Else
  313.                 TrimTail0$ = Mid$(copys$, 1, i)
  314.             End If
  315.         End If
  316.     End If
  317.  
  318. Function trim0$ (s$)
  319.     Dim cs$, si$
  320.     cs$ = s$
  321.     si$ = Left$(cs$, 1)
  322.     If si$ = "-" Then cs$ = Mid$(cs$, 2)
  323.     cs$ = TrimLead0$(cs$)
  324.     cs$ = TrimTail0$(cs$)
  325.     If Right$(cs$, 1) = "." Then cs$ = Mid$(cs$, 1, Len(cs$) - 1)
  326.     If si$ = "-" Then trim0$ = si$ + cs$ Else trim0$ = cs$
  327.  
  328. ' for displaying truncated numbers say to 60 digits
  329. Function showDP$ (num$, nDP As Integer)
  330.     Dim cNum$, dp As Integer, d As Integer, i As Integer
  331.     cNum$ = num$ 'since num$ could get changed
  332.     showDP$ = num$
  333.     dp = InStr(num$, ".")
  334.     If dp > 0 Then
  335.         If Len(Mid$(cNum$, dp + 1)) > nDP Then
  336.             d = Val(Mid$(cNum$, dp + nDP + 1, 1))
  337.             If d > 4 Then
  338.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  339.                 dp = dp + 1
  340.                 i = dp + nDP
  341.                 While Mid$(cNum$, i, 1) = "9" Or Mid$(cNum$, i, 1) = "."
  342.                     If Mid$(cNum$, i, 1) = "9" Then
  343.                         Mid$(cNum$, i, 1) = "0"
  344.                     End If
  345.                     i = i - 1
  346.                 Wend
  347.                 Mid$(cNum$, i, 1) = _Trim$(Str$(Val(Mid$(cNum$, i, 1)) + 1)) 'last non 9 digit
  348.                 cNum$ = Mid$(cNum$, 1, dp + nDP) 'chop it
  349.                 showDP$ = trim0$(cNum$)
  350.             Else
  351.                 showDP$ = Mid$(cNum$, 1, dp + nDP)
  352.             End If
  353.         End If
  354.     End If
  355.  

Going back to oh to replace the Divide$ function there.

Here is a list of compares between Square Root from above code and an Internet Calculator (that rounds it's last digit in decimal place, whereas this code cuts off at that digit # not decimal place.)
Code: [Select]
First show code clipboard result then show Internet Calculator result
from https://www.mathsisfun.com/calculator-precision.html

Square root 2 first from String Math then Internet Result 
1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572
1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727

Square root of 3...
1.732050807568877293527446341505872366942805253810380628055806979451933016908800037081146186757248575
1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485757

Square root of 4...
gets stuck in infinite loop 1.999999999999999999999999....   
2 of course!

OK compare guess to last guess and if they match out to 105 digits then 100 digits must be accurate
1.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
2 of course!

SR 5
2.236067977499789696409173668731276235440618359611525724270897245410520925637804899414414408378782274
2.23606797749978969640917366873127623544061835961152572427089724541052092563780489941441440837878227497

SR 6
2.449489742783178098197284074705891391965947480656670128432692567250960377457315026539859433104640234
2.44948974278317809819728407470589139196594748065667012843269256725096037745731502653985943310464023482

SR 7
2.645751311064590590501615753639260425710259183082450180368334459201068823230283627760392886474543610
2.64575131106459059050161575363926042571025918308245018036833445920106882323028362776039288647454361062
____________________________________________________________________
George McGinn
Theoretical/Applied Computer Scientist
Member: IEEE, IEEE Computer Society
Technical Council on Software Engineering
IEEE Standards Association
American Association for the Advancement of Science (AAAS)

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
Re: String Math
« Reply #28 on: June 03, 2021, 03:55:53 pm »
hi bplus
here's some interesting code that if you manage to translate to QB64 might be useful as a first approximation for the Newton-Raphson Method
this is FreeBbasic code but the code is very short and the algorithm is explained in comments

Code: [Select]
'=======================================================================
' approximate a ^ b
Function approx_power( Byval a As Double, Byval b As Double ) As Double
    Dim As Long Ptr fp = 1 + Cptr( Long Ptr, @a )
    Dim As Long tmp = ( *fp - 1072632447 ) * b
    *fp = tmp + 1072632447
    Return a
End Function

'=======================================================================
' How and Why It Works.
' To accurately compute  y = a^b;  for non-integer b;  with a > 0;
'   take the log(a), multiply it by b, then look up the antilog, which is
'   the Exp() function, to give; y = Exp( b * Log( a ) )
' For a computer using base 2 it is convenient to use; y = 2^( b * Log2(a) )
' The IEEE double precision floating point format has an 11 bit, base two,
'   integer exponent. That will become the integer part of the log2(x) function.
'   It is simple to extract the exponent, multiply it by b, then return it to
'   the exponent, but that would be a clunky approximation because the exponent
'   is an integer count of powers of two with poor resolution.
' So that the multiply will change the result for small changes of a or power b,
'   the leading 20 mantissa bits are included as a false fractional extension to
'   the logarithmic exponent. That is the source of error in this approximation.
'   Note that the implicit 1 msb is missing from the mantissa.
' The exponent offset bias of 1022 must be removed by subtraction before the
'   multiplication. In the same operation an amount can be deducted from the
'   mantissa to significantly reduce the approximation error. The approximation
'   is computed from only the top 32 bits of the 64 bit double precision format.
' &bSeeeeeeeeeeeffffffffffffffffffff  Sign bit, exponent and fraction format
' &b00111111111000000000000000000000    1071644672 = 1022 * 2^20, shifted bias
' &b00000000000011110001001001111111        987775 = fudge amount
' &b00111111111011110001001001111111    1072632447 = sum is the magic number
'=======================================================================
dim as double x, y, z

Print approx_power( 123.456789, .5 ), sqr(123.456789)
here's the QB64 version
Code: QB64: [Select]
  1.     Sub memcpy (ByVal dest As _Offset, Byval source As _Offset, Byval bytes As Long)
  2.  
  3.  
  4. Print approx_power(20000, .5#), Sqr(20000#)
  5.  
  6. Function approx_power# (x#, y#)
  7.     Dim As Double a
  8.     Dim As Long tmp
  9.     a = x#
  10.     memcpy _Offset(tmp), _Offset(a) + 4, 4
  11.     tmp = (tmp - 1072632447) * y#
  12.     tmp = tmp + 1072632447
  13.     memcpy _Offset(a) + 4, _Offset(tmp), 4
  14.     approx_power = a
  15.  
all that's needed is to add the Newton-Raphson code, for reciprocal powers of integers it should give a fast approximation
« Last Edit: June 06, 2021, 10:15:00 am by jack »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: String Math
« Reply #29 on: June 03, 2021, 04:14:07 pm »
Oh I see while I was making tiny improvements, @GeorgeMcGinn and @jack have suggested more.
OK I see you both like Newton-Raphson, I've see it like 50 years ago in math class so must be better. I will try and work it that way then.

In meantime I've chosen other$ to return with function because sometimes it nails the answer precisely without a bunch of .99999999's on end, not always but less than guess$. I am also handling negative numbers by tacking on an *i to the end of the number returned, better than error message, maybe.

Code: QB64: [Select]
  1. _Title "Math regulator mr test sqr estimating" ' b+ start 2021-06-02
  2. ' divide$ found fix? 2021-06-03 comment in that function
  3. ' 2021-06-03 update with new sqrRoot$ Function
  4.  
  5. Randomize Timer 'now that it's seems to be running silent
  6. Screen _NewImage(1200, 700, 32)
  7. _Delay .25
  8.  
  9. Dim n$, result$
  10.     'remember everything is strings
  11.     Input "Enter a number to find it's square root "; n$
  12.     If n$ = "" Then End
  13.     result$ = sqrRoot$(n$)
  14.     Print result$
  15.     Print "Length ="; Len(result$)
  16.     Print
  17.  
  18. Function sqrRoot$ (nmbr$)
  19.     Dim n$, guess$, lastGuess$, other$, sum$, imaginary$
  20.     If Left$(nmbr$, 1) = "-" Then 'handle neg numbers
  21.         imaginary$ = "*i": n$ = Mid$(nmbr$, 2)
  22.     Else
  23.         imaginary$ = "": n$ = nmbr$
  24.     End If
  25.     guess$ = mr$(n$, "/", "2")
  26.     other$ = n$
  27.     Do
  28.         If (Mid$(guess$, 1, 105) = Mid$(lastGuess$, 1, 105)) Then ' go past 100 matching digits for 100 digit precision
  29.             sqrRoot$ = Mid$(other$, 1, 101) + imaginary$
  30.             Exit Function
  31.         Else
  32.             lastGuess$ = guess$
  33.             sum$ = mr$(guess$, "+", other$)
  34.             guess$ = mr$(sum$, "/", "2")
  35.             other$ = mr$(n$, "/", guess$)
  36.         End If
  37.     Loop
  38.  
  39. Function mr$ (a$, op$, b$) ' catchy? mr$ for math regulator
  40.     Dim ca$, cb$, aSgn$, bSgn$, postOp$, sgn$
  41.     Dim adp As Integer, bdp As Integer, dp As Integer, lpop As Integer
  42.  
  43.     op$ = _Trim$(op$) 'save fixing each time
  44.     ca$ = _Trim$(a$): cb$ = _Trim$(b$) 'make copies in case we change
  45.     'strip signs and decimals
  46.     If Left$(ca$, 1) = "-" Then
  47.         aSgn$ = "-": ca$ = Mid$(ca$, 2)
  48.     Else
  49.         aSgn$ = "": ca$ = ca$
  50.     End If
  51.     dp = InStr(ca$, ".")
  52.     If dp > 0 Then
  53.         adp = Len(ca$) - dp
  54.         ca$ = Mid$(ca$, 1, dp - 1) + Mid$(ca$, dp + 1)
  55.     Else
  56.         adp = 0
  57.     End If
  58.     If Left$(cb$, 1) = "-" Then
  59.         bSgn$ = "-": cb$ = Mid$(cb$, 2)
  60.     Else
  61.         bSgn$ = "": cb$ = cb$
  62.     End If
  63.     dp = InStr(cb$, ".")
  64.     If dp > 0 Then
  65.         bdp = Len(cb$) - dp
  66.         cb$ = Mid$(cb$, 1, dp - 1) + Mid$(cb$, dp + 1)
  67.     Else
  68.         bdp = 0
  69.     End If
  70.  
  71.     If op$ = "+" Or op$ = "-" Or op$ = "/" Then 'add or subtr  even up strings on right of decimal
  72.         'even up the right sides of decimals if any
  73.         If adp > bdp Then dp = adp Else dp = bdp
  74.         If adp < dp Then ca$ = ca$ + String$(dp - adp, "0")
  75.         If bdp < dp Then cb$ = cb$ + String$(dp - bdp, "0")
  76.     ElseIf op$ = "*" Then
  77.         dp = adp + bdp
  78.     End If
  79.     If op$ = "*" Or op$ = "/" Then
  80.         If aSgn$ = bSgn$ Then sgn$ = "" Else sgn$ = "-"
  81.     End If
  82.     'now according to signs and op$ call add$ or subtr$
  83.     If op$ = "+" Then
  84.         If aSgn$ = bSgn$ Then 'really add
  85.             postOp$ = aSgn$ + add$(ca$, cb$)
  86.         Else 'have a case of subtraction
  87.             If aSgn$ = "-" Then postOp$ = subtr$(cb$, ca$) Else postOp$ = subtr$(ca$, cb$)
  88.         End If
  89.     ElseIf op$ = "-" Then
  90.         If bSgn$ = "-" Then 'really add but switch b sign
  91.             bSgn$ = ""
  92.             If aSgn$ = "-" Then
  93.                 postOp$ = subtr$(cb$, ca$)
  94.             Else 'aSgn = ""
  95.                 postOp$ = add$(ca$, cb$)
  96.             End If
  97.         Else 'bSgn$ =""
  98.             If aSgn$ = "-" Then
  99.                 bSgn$ = "-"
  100.                 postOp$ = aSgn$ + add$(ca$, cb$)
  101.             Else
  102.                 postOp$ = subtr$(ca$, cb$)
  103.             End If
  104.         End If
  105.     ElseIf op$ = "*" Then
  106.         postOp$ = sgn$ + mult$(ca$, cb$)
  107.     ElseIf op$ = "/" Then
  108.         postOp$ = sgn$ + divide$(ca$, cb$)
  109.     End If ' which op
  110.     'put dp back
  111.     If op$ <> "/" Then
  112.         lpop = Len(postOp$) ' put decimal back
  113.         postOp$ = Mid$(postOp$, 1, lpop - dp) + "." + Mid$(postOp$, lpop - dp + 1)
  114.     End If
  115.     mr$ = trim0$(postOp$)
  116.  
  117. Function divide$ (n$, d$) ' goal here is 100 digits precision not 100 digits past decimal
  118.     Dim di$, ndi$, nD As Integer
  119.     If trim0$(n$) = "0" Then divide$ = "0": Exit Function
  120.     If trim0$(d$) = "0" Then divide$ = "div 0": Exit Function
  121.     If trim0$(d$) = "1" Then divide$ = trim0$(n$): Exit Function '8/17 add trim0$
  122.  
  123.     ' aha! found a bug when d$ gets really huge 100 is no where near enough!!!!
  124.     ' 2021-06-03 fix by adding 100 to len(d$), plus have to go a little past 100 for 100 digit precision
  125.     ' need to go past 100 for 100 precise digits (not decimal places)
  126.     di$ = Mid$(nInverse$(d$, Len(d$) + 200), 2) 'chop off decimal point after
  127.     nD = Len(di$)
  128.     ndi$ = mult$(n$, di$)
  129.     ndi$ = Mid$(ndi$, 1, Len(ndi$) - nD) + "." + Right$(String$(nD, "0") + Right$(ndi$, nD), nD)
  130.     divide$ = trim0$(ndi$)
  131.  
  132. Function nInverse$ (n$, DP As Integer) 'assume decimal at very start of the string of digits returned, no rounding
  133.     Dim m$(1 To 9), si$, r$, outstr$, d$
  134.     Dim i As Integer
  135.     For i = 1 To 9
  136.         si$ = _Trim$(Str$(i))
  137.         m$(i) = mult$(si$, n$)
  138.     Next
  139.     outstr$ = ""
  140.     If n$ = "0" Then nInverse$ = "Div 0": Exit Function
  141.     If n$ = "1" Then nInverse$ = "1": Exit Function
  142.     outstr$ = "." 'everything else n > 1 is decimal 8/17
  143.     r$ = "10"
  144.     Do
  145.         While Left$(subtr$(r$, n$), 1) = "-" '   r - n < 0
  146.             outstr$ = outstr$ + "0" '            add 0 to the  output string
  147.             If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function 'check if we've reached DP length
  148.             r$ = r$ + "0"
  149.         Wend
  150.         For i = 9 To 1 Step -1
  151.             If LTE(m$(i), r$) Then d$ = _Trim$(Str$(i)): Exit For
  152.         Next
  153.         outstr$ = outstr$ + d$
  154.         If Len(outstr$) = DP Then nInverse$ = outstr$: Exit Function
  155.         r$ = subtr$(r$, mult$(d$, n$)) 'r = r -d*n
  156.         If r$ = "0" Then nInverse$ = outstr$: Exit Function 'found a perfect divisor
  157.         r$ = r$ + "0" 'add another place
  158.     Loop
  159.  
  160. Function mult$ (a$, b$) 'assume both positive integers prechecked as all digits strings
  161.     Dim la As Integer, lb As Integer, m As Integer, g As Integer, dp As Integer
  162.     Dim f18$, f1$, t$, build$, accum$
  163.  
  164.     If trim0$(a$) = "0" Then mult$ = "0": Exit Function
  165.     If trim0$(b$) = "0" Then mult$ = "0": Exit Function
  166.     If trim0$(a$) = "1" Then mult$ = trim0$(b$): Exit Function
  167.     If trim0$(b$) = "1" Then mult$ = trim0$(a$): Exit Function
  168.     'find the longer number and make it a mult of 18 to take 18 digits at a time from it
  169.     la = Len(a$): lb = Len(b$)
  170.     If la > lb Then
  171.         m = Int(la / 18) + 1
  172.         f18$ = Right$(String$(m * 18, "0") + a$, m * 18)
  173.         f1$ = b$
  174.     Else
  175.         m = Int(lb / 18) + 1
  176.         f18$ = Right$(String$(m * 18, "0") + b$, m * 18)
  177.         f1$ = a$
  178.     End If
  179.     For dp = Len(f1$) To 1 Step -1 'dp = digit position of the f1$
  180.         build$ = "" 'line builder
  181.         co = 0
  182.         'now taking 18 digits at a time Thanks Steve McNeill
  183.         For g = 1 To m
  184.             v18 = Val(Mid$(f18$, m * 18 - g * 18 + 1, 18))
  185.             sd = Val(Mid$(f1$, dp, 1))
  186.             t$ = Right$(String$(19, "0") + _Trim$(Str$(v18 * sd + co)), 19)
  187.             co = Val(Mid$(t$, 1, 1))
  188.             build$ = Mid$(t$, 2) + build$
  189.         Next g
  190.         If co Then build$ = _Trim$(Str$(co)) + build$
  191.         If dp = Len(f1$) Then
  192.             accum$ = build$
  193.         Else
  194.             accum$ = add$(accum$, build$ + String$(Len(f1$) - dp, "0"))
  195.         End If
  196.     Next dp
  197.     mult$ = accum$
  198.  
  199. Function subtr$ (sum$, minus$) ' assume both numbers are positive all digits
  200.     Dim m As Integer, g As Integer, p As Integer
  201.     Dim ts$, tm$, sign$, LG$, sm$, t$, result$
  202.  
  203.     ts$ = TrimLead0$(sum$): tm$ = TrimLead0$(minus$)
  204.     If trim0(ts$) = trim0$(tm$) Then subtr$ = "0": Exit Function 'OK proceed with function knowing they are not equal
  205.     tenE18 = 1000000000000000000 'yes!!! no dang E's
  206.     If LTE(ts$, tm$) Then ' which is bigger? minus is bigger
  207.         sign$ = "-"
  208.         m = Int(Len(tm$) / 18) + 1
  209.         LG$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  210.         sm$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  211.     Else 'sum is bigger
  212.         sign$ = ""
  213.         m = Int(Len(ts$) / 18) + 1
  214.         LG$ = Right$(String$(m * 18, "0") + ts$, m * 18)
  215.         sm$ = Right$(String$(m * 18, "0") + tm$, m * 18)
  216.     End If
  217.     'now taking 18 digits at a time From Steve I learned we can do more than 1 digit at a time
  218.     For g = 1 To m
  219.         VB = Val(Mid$(LG$, m * 18 - g * 18 + 1, 18))
  220.         vs = Val(Mid$(sm$, m * 18 - g * 18 + 1, 18))
  221.         If vs > VB Then
  222.             t$ = Right$(String$(18, "0") + _Trim$(Str$(tenE18 - vs + VB)), 18)
  223.             p = (m - g) * 18
  224.             While p > 0 And Mid$(LG$, p, 1) = "0"
  225.                 Mid$(LG$, p, 1) = "9"
  226.                 p = p - 1
  227.             Wend
  228.             If p > 0 Then Mid$(LG$, p, 1) = _Trim$(Str$(Val(Mid$(LG$, p, 1)) - 1))
  229.         Else
  230.             t$ = Right$(String$(18, "0") + _Trim$(Str$(VB - vs)), 18)
  231.         End If
  232.         result$ = t$ + result$
  233.     Next
  234.     subtr$ = sign$ + result$
  235.  
  236. Function add$ (a$, b$) 'add 2 positive integers assume a and b are just numbers no spaces or - signs
  237.     'first thing is to set a and b numbers to same length and multiple of 18 so can take 18 digits at a time
  238.     Dim la As Integer, lb As Integer, m As Integer, g As Integer
  239.     Dim fa$, fb$, t$, new$, result$
  240.     la = Len(a$): lb = Len(b$)
  241.     If la > lb Then m = Int(la / 18) + 1 Else m = Int(lb / 18) + 1
  242.     fa$ = Right$(String$(m * 18, "0") + a$, m * 18)
  243.     fb$ = Right$(String$(m * 18, "0") + b$, m * 18)
  244.  
  245.     'now taking 18 digits at a time Thanks Steve McNeill
  246.     For g = 1 To m
  247.         sa = Val(Mid$(fa$, m * 18 - g * 18 + 1, 18))
  248.         sb = Val(Mid$(fb$, m * 18 - g * 18 + 1, 18))
  249.         t$ = Right$(String$(36, "0") + _Trim$(Str$(sa + sb + co)), 36)
  250.         co = Val(Mid$(t$, 1, 18))
  251.         new$ = Mid$(t$, 19)
  252.         result$ = new$ + result$
  253.     Next
  254.     If co Then result$ = Str$(co) + result$
  255.     add$ = result$
  256.  
  257. ' String Math Helpers -----------------------------------------------
  258.  
  259. 'this function needs TrimLead0$(s$)
  260. Function LTE (a$, b$) ' a$ Less Than or Equal b$  comparison of 2 strings
  261.     Dim ca$, cb$, la As Integer, lb As Integer, i As Integer
  262.     ca$ = TrimLead0$(a$): cb$ = TrimLead0(b$)
  263.     la = Len(ca$): lb = Len(cb$)
  264.     If ca$ = cb$ Then
  265.         LTE = -1
  266.     ElseIf la < lb Then ' a is smaller
  267.         LTE = -1
  268.     ElseIf la > lb Then ' a is bigger
  269.         LTE = 0
  270.     ElseIf la = lb Then ' equal lengths
  271.         For i = 1 To Len(ca$)
  272.             If Val(Mid$(ca$, i, 1)) > Val(Mid$(cb$, i, 1)) Then
  273.                 LTE = 0: Exit Function
  274.             ElseIf Val(Mid$(ca$, i, 1)) < Val(Mid$(cb$, i, 1)) Then
  275.                 LTE = -1: Exit Function
  276.             End If
  277.         Next
  278.     End If
  279.  
  280. ' ------------------------------------- use these for final display
  281.  
  282. Function TrimLead0$ (s$) 'for treating strings as number (pos integers)
  283.     Dim copys$, i As Integer, find As Integer
  284.     copys$ = _Trim$(s$) 'might as well remove spaces too
  285.     i = 1: find = 0
  286.     While i < Len(copys$) And Mid$(copys$, i, 1) = "0"
  287.         i = i + 1: find = 1
  288.     Wend
  289.     If find = 1 Then copys$ = Mid$(copys$, i)
  290.     If copys$ = "" Then TrimLead0$ = "0" Else TrimLead0$ = copys$
  291.  
  292. Function TrimTail0$ (s$)
  293.     Dim copys$, dp As Integer, i As Integer, find As Integer
  294.     copys$ = _Trim$(s$) 'might as well remove spaces too
  295.     TrimTail0$ = copys$
  296.     dp = InStr(copys$, ".")
  297.     If dp > 0 Then
  298.         i = Len(copys$): find = 0
  299.         While i > dp And Mid$(copys$, i, 1) = "0"
  300.             i = i - 1: find = 1
  301.         Wend
  302.         If find = 1 Then
  303.             If i = dp Then
  304.                 TrimTail0$ = Mid$(copys$, 1, dp - 1)
  305.             Else
  306.                 TrimTail0$ = Mid$(copys$, 1, i)
  307.             End If
  308.         End If
  309.     End If
  310.  
  311. Function trim0$ (s$)
  312.     Dim cs$, si$
  313.     cs$ = s$
  314.     si$ = Left$(cs$, 1)
  315.     If si$ = "-" Then cs$ = Mid$(cs$, 2)
  316.     cs$ = TrimLead0$(cs$)
  317.     cs$ = TrimTail0$(cs$)
  318.     If Right$(cs$, 1) = "." Then cs$ = Mid$(cs$, 1, Len(cs$) - 1)
  319.     If si$ = "-" Then trim0$ = si$ + cs$ Else trim0$ = cs$
  320.  
  321. ' for displaying truncated numbers say to 60 digits
  322. Function showDP$ (num$, nDP As Integer)
  323.     Dim cNum$, dp As Integer, d As Integer, i As Integer
  324.     cNum$ = num$ 'since num$ could get changed
  325.     showDP$ = num$
  326.     dp = InStr(num$, ".")
  327.     If dp > 0 Then
  328.         If Len(Mid$(cNum$, dp + 1)) > nDP Then
  329.             d = Val(Mid$(cNum$, dp + nDP + 1, 1))
  330.             If d > 4 Then
  331.                 cNum$ = "0" + cNum$ ' tack on another 0 just in case 9's all the way to left
  332.                 dp = dp + 1
  333.                 i = dp + nDP
  334.                 While Mid$(cNum$, i, 1) = "9" Or Mid$(cNum$, i, 1) = "."
  335.                     If Mid$(cNum$, i, 1) = "9" Then
  336.                         Mid$(cNum$, i, 1) = "0"
  337.                     End If
  338.                     i = i - 1
  339.                 Wend
  340.                 Mid$(cNum$, i, 1) = _Trim$(Str$(Val(Mid$(cNum$, i, 1)) + 1)) 'last non 9 digit
  341.                 cNum$ = Mid$(cNum$, 1, dp + nDP) 'chop it
  342.                 showDP$ = trim0$(cNum$)
  343.             Else
  344.                 showDP$ = Mid$(cNum$, 1, dp + nDP)
  345.             End If
  346.         End If
  347.     End If
  348.  
Test sqrRoot$.PNG



I was all set to make this the new Best Answer but I will investigate adapting Newton-Raphson, a better start from jack and a better method from george.
« Last Edit: June 03, 2021, 04:19:36 pm by bplus »