'=========================================================================== ' Subject: GREGORY'S SERIES Date: 02-28-99 (23:41) ' Author: Jason Stratos Papadopoulos Code: QB, QBasic, PDS ' Origin: jasonp@Glue.umd.edu Packet: ALGOR.ABC '=========================================================================== DECLARE SUB lastmult (digits&) DECLARE SUB trickmult (j&) DECLARE SUB addterm (j&) DECLARE SUB printout (sum&()) CLS DEFLNG A-Z ' You may be familiar with Gregory's series: ' ' pi 1 1 1 1 ' -- = 1 - - + - - - + - - ... ' 4 3 5 7 9 ' ' It's a prime example of reducing a mysterious number like pi to boring ' arithmetic. Unfortunately it's also a prime example of a *slow* infinite ' series! Since the terms alternate and decrease in size, the error involved ' in stopping at a certain point is bounded by the first term ignored; to get ' 5 digits of pi you'd have to add up 2*10^5 terms of the series, since each ' extra decimal means 10 times the accuracy means 10 times the work. For this ' reason nobody uses Gregory's series to compute anything, until now. ' ' This program lets you peer into the sum of Gregory's series far deeper than ' is ordinarily possible, by allowing one to add up enormous numbers of terms ' quickly. The starting point is a strange series for pi/4: ' ' pi 5 11 1*2 17 1*2 3*4 23 1*2 3*4 5*6 ' -- = - - -- ----- + -- ----- ----- - -- ----- ----- ------- + ... ' 4 6 14 3*5*2 22 3*5*2 7*9*2 30 3*5*2 7*9*2 11*13*2 ' ' which can be found by a transformation of Gregory's series. Each term in ' this series is about 1/8 the size of the previous one, so it converges at ' about .9 digits per term...not bad, but not spectacular. What's very sur- ' prising is that the above can be slightly modified to converge to a ' *finite* sum of the terms in Gregory's series. For n even, ' ' 1 1 1 1 2n / 5 11 1*2*(n-2) ' 1 - - + - - - + ... - ---- = ---- | - - -- ---------- ' 3 5 7 2n-1 2n+1 \ 6 14 3*5*(2n+3) ' ' 17 1*2*(n-2) 3*4*(n-4) 23 1*2*(n-2) 3*4*(n-4) 5*6*(n-6) \ ' + -- ---------- ---------- - -- ---------- ---------- ----------- +... | ' 22 3*5*(2n+3) 7*9*(2n+5) 30 3*5*(2n+3) 7*9*(2n+5) 11*13*(2n+7) / ' ' Of course, as n becomes infinite the second series reduces to the first. ' It looks like a huge mess, but it's pretty neat: the above says that you ' can dial in a value of n, and you have a series that converges to the sum ' of n terms of Gregory's series at .9 digits per term, no matter how big ' n is. Hence 100 terms can get 90 digits of the sum, whether n is a thousand ' or a trillion! ' ' Combined with some multiple precision arithmetic and a few tricks for ' dividing by huge numbers, the result is pretty impressive. This program ' can calculate (to tens of thousands of digits) the sum of an incredible ' number of terms in Gregory's series, 10^5 or 10^50 or 10^1000 or whatever; ' and it doesn't need the age of the universe to do so. So the next time ' some C programmer gives you lip about how slow Basic is, ask him/her to ' time adding up 10^20 terms of Gregory's series, to 1000 digits of accuracy; ' my 486 using the compiled version of this program needs 59 seconds. ' ' A well-documented phenomenon you'll find is that when adding up numbers ' of terms that are multiples of 2 and 5, what you'll see is one wrong digit, ' then lots of correct ones, then one wrong digit, then lots of correct ones, ' and so on for a long way past the decimal point. PRINT "This program adds up 4 * Gregory's series (1 - 1/3 + 1/5 - 1/7 +...)" INPUT "How many digits do you wish to carry the sum to"; digits: PRINT PRINT "How many terms do you wish added up? Express your answer as" PRINT "two numbers, m and e, where the number of terms is m * 10^e;" PRINT "so for 600,000 terms type ' 6 , 5 '. Please make m<10 and e>=5" start: INPUT mantissa%, exponent%: PRINT IF exponent% < 5 or mantissa% > 9 THEN PRINT "Read the instructions, and try again..." GOTO start END IF SELECT CASE exponent% MOD 4 'divide exponent by 4; if CASE 0 'it isn't evenly divisible by 4, exponent% = exponent% \ 4 'absorb the difference into the mantissa% = mantissa% * 2 'mantissa CASE 1 exponent% = (exponent% - 1) \ 4 mantissa% = mantissa% * 20 CASE 2 exponent% = (exponent% - 2) \ 4 mantissa% = mantissa% * 200 CASE 3 exponent% = (exponent% - 3) \ 4 mantissa% = mantissa% * 2000 END SELECT digits = digits \ 4 + 3 'initialize DIM SHARED sum(digits + 1) DIM SHARED term1(digits + 1) DIM SHARED term2(digits + 1) DIM SHARED term3(digits + 1) j = 1: firstword = 1: term1(1) = 1 sum(1) = 3: start! = TIMER FOR i = 2 TO digits: sum(i) = 3333: NEXT i 'first term WHILE firstword < digits 'main computation CALL addterm(j) j = j + 2 WEND CALL lastmult(digits) FOR j = digits TO 2 STEP -1 'release carries and borrows IF sum(j) < 0 THEN quotient = sum(j) \ 10000 sum(j - 1) = sum(j - 1) + quotient - 1 sum(j) = sum(j) - 10000 * quotient + 10000 END IF IF sum(j) >= 10000 THEN quotient = sum(j) \ 10000 sum(j - 1) = sum(j - 1) + quotient sum(j) = sum(j) - 10000 * quotient END IF NEXT j finish! = TIMER CALL printout(sum()) PRINT "Computation finished in "; finish! - start!; "seconds" END '------------------------------------------------------------------------ SUB addterm (j) SHARED firstword, digits CALL trickmult(j) denom1 = 4 * j - 1: denom2 = denom1 + 2: denom3 = denom1 + 4 remainder1 = 0: remainder2 = 0 remainder3 = 0: remainder4 = 0 FOR i = firstword TO digits tempterm = term1(i) 'multiply by (2j-1)/(4j-1) dividend = remainder1 * 10000 + tempterm temp = dividend \ denom1 remainder1 = dividend - denom1 * temp tempterm = tempterm - temp dividend = remainder2 * 10000 + tempterm 'multiply by (2j+1)/(4j+3) temp = dividend \ denom2 remainder2 = dividend - denom2 * temp tempterm = tempterm - temp dividend = remainder3 * 10000 + tempterm 'divide by 8 tempterm = dividend \ 8 remainder3 = dividend - 8 * tempterm dividend = remainder4 * 10000 + tempterm 'scale the term and temp = dividend \ denom3 'subtract from the sum remainder4 = dividend - denom3 * temp sum(i) = sum(i) - temp - tempterm - tempterm - tempterm term1(i) = tempterm NEXT i denom1 = denom1 + 4: denom2 = denom1 + 2: denom3 = denom1 + 4 remainder1 = 0: remainder2 = 0 remainder3 = 0: remainder4 = 0 CALL trickmult(j + 1) FOR i = firstword TO digits tempterm = term1(i) 'multiply by (2j+3)/(4j+7) dividend = remainder1 * 10000 + tempterm temp = dividend \ denom1 remainder1 = dividend - denom1 * temp tempterm = tempterm - temp dividend = remainder2 * 10000 + tempterm 'multiply by (2j+5)/(4j+9) temp = dividend \ denom2 remainder2 = dividend - denom2 * temp tempterm = tempterm - temp dividend = remainder3 * 10000 + tempterm 'divide by 8 tempterm = dividend \ 8 remainder3 = dividend - 8 * tempterm dividend = remainder4 * 10000 + tempterm 'scale the term and temp = dividend \ denom3 'add to the sum remainder4 = dividend - denom3 * temp sum(i) = sum(i) + temp + tempterm + tempterm + tempterm term1(i) = tempterm NEXT i IF term1(firstword) = 0 THEN firstword = firstword + 1 END SUB '------------------------------------------------------------------------- SUB lastmult (digits) SHARED mantissa%, exponent% ' as the name implies, this subroutine performs the final scaling ' operation, by multiplying sum() by 1 - 1/(m*10^e + 1). The code looks ' quite a bit like the code in trickmult, but is much simpler since no ' checking is needed for overflow. sourceword = 1: remainder = 0 destword = 1 + exponent% FOR i = destword TO digits dividend = remainder * 10000 + sum(sourceword) temp = dividend \ mantissa% remainder = dividend - temp * mantissa% term2(sourceword) = temp sourceword = sourceword + 1 NEXT i sourceword = 1 FOR i = destword TO digits sum(i) = sum(i) - term2(sourceword) sourceword = sourceword + 1 NEXT i sourceword = 1: remainder = 0 destword = destword + exponent% DO UNTIL destword > digits FOR i = destword TO digits temp = term2(sourceword) dividend = remainder * 10000 + temp temp = dividend \ mantissa% remainder = dividend - temp * mantissa% sum(i) = sum(i) + temp term2(sourceword) = temp sourceword = sourceword + 1 NEXT i sourceword = 1: remainder = 0 destword = destword + exponent% FOR i = destword TO digits temp = term2(sourceword) dividend = remainder * 10000 + temp temp = dividend \ mantissa% remainder = dividend - temp * mantissa% sum(i) = sum(i) - temp term2(sourceword) = temp sourceword = sourceword + 1 NEXT i sourceword = 1: remainder = 0 destword = destword + exponent% LOOP END SUB '------------------------------------------------------------------------ SUB printout (sum()) SHARED digits PRINT "the sum is 3+." FOR i = 1 TO digits \ 3 PRINT " "; PRINT RIGHT$("0000" + LTRIM$(STR$(sum(3 * (i - 1) + 2))), 4); PRINT RIGHT$("0000" + LTRIM$(STR$(sum(3 * (i - 1) + 3))), 4); PRINT RIGHT$("0000" + LTRIM$(STR$(sum(3 * (i - 1) + 4))), 4); IF i MOD 5 = 0 THEN PRINT " :"; 12 * i NEXT i PRINT " "; FOR i = 3 * (digits \ 3) + 2 TO digits PRINT RIGHT$("0000" + LTRIM$(STR$(sum(i))), 4); NEXT i PRINT : PRINT END SUB '-------------------------------------------------------------------------- SUB trickmult (j) SHARED mantissa%, exponent%, digits, firstword ' this subroutine multiplies term1() by 1 - (6j+1)/(m * 10^e + 2j+1). It ' isn't easy, since m*10^e can be a huge number like 10^50; rather than using ' multiple precision long division (which is quite slow) I use a dirty trick ' that relies on the huge number being a power of 10; in that case, dividing ' by 10^50 is easy, just shift the decimal point 50 places to the left (sym- ' bolically)! Of course, this program works in base 10000 instead of 10, but ' the principle's the same. One last dirty trick is to expand the multiplier ' as a geometric series: ' ' 6j+1 6j+1 / 2j+1 2j+1 2 2j+1 3 \ ' ------------- = ------ | 1 - ------ + ( ------ ) - ( ------ ) + ... | ' m*10^e + 2j+1 m*10^e \ m*10^e m*10^e m*10^e / ' ' thus reducing the problem to multiplies and divides by small numbers ' (m and 2j+1), and shifts by a (possibly huge) exponent. It still isn't ' blazing fast, but it works nicely. num = 6 * j + 1: remainder = 0 sourceword = firstword destword = firstword + exponent% term2(destword - 1) = 0 FOR i = destword TO digits dividend = remainder * 10000 + num * term1(sourceword) temp = dividend \ mantissa% remainder = dividend - temp * mantissa% 'create temporary term sourceword = sourceword + 1 term2(i) = temp 'term2 <- term1 * num / m*10^e NEXT i FOR i = destword TO digits temp = term2(i) IF temp >= 10000 OR temp <= -10000 THEN quotient = temp \ 10000 'release carries term2(i - 1) = term2(i - 1) + quotient term1(i - 1) = term1(i - 1) - quotient temp = temp - 10000 * quotient END IF term1(i) = term1(i) - temp 'term1 <- term1 - term2 term2(i) = temp NEXT i IF term2(destword - 1) <> 0 THEN destword = destword - 1 num = 2 * j + 1 sourceword = destword destword = destword + exponent% DO UNTIL destword > digits term3(destword - 1) = 0: remainder = 0 FOR i = destword TO digits 'term3 <- term2 * num / m*10^e temp = term2(sourceword) dividend = remainder * 10000 + num * temp temp = dividend \ mantissa% remainder = dividend - temp * mantissa% IF temp >= 10000 OR temp <= -10000 THEN 'release carries quotient = temp \ 10000 term3(i - 1) = term3(i - 1) + quotient term1(i - 1) = term1(i - 1) + quotient temp = temp - 10000 * quotient END IF 'term1 <- term1 + term3 term1(i) = term1(i) + temp term3(i) = temp sourceword = sourceword + 1 NEXT i IF term3(destword - 1) <> 0 THEN destword = destword - 1 sourceword = destword: remainder = 0 destword = destword + exponent% IF destword <= digits THEN term2(destword - 1) = 0 FOR i = destword TO digits 'term2 <- term3 * num / m*10^e temp = term3(sourceword) dividend = remainder * 10000 + num * temp temp = dividend \ mantissa% remainder = dividend - temp * mantissa% IF temp >= 10000 OR temp <= -10000 THEN 'release carries quotient = temp \ 10000 term2(i - 1) = term2(i - 1) + quotient term1(i - 1) = term1(i - 1) - quotient temp = temp - 10000 * quotient END IF term1(i) = term1(i) - temp 'term1 <- term1 + term2 term2(i) = temp sourceword = sourceword + 1 NEXT i IF term2(destword - 1) <> 0 THEN destword = destword - 1 sourceword = destword destword = destword + exponent% LOOP END SUB