Python
13.3 Gregory's Series
and Other Bases

 


Conversions Between Number Bases: In order to examine Gregory's series using other number bases, I needed Python code to convert between bases. I didn't try to find Python libraries that would do this.

First note that for a number base b and a number n, the expression logb(n) gives the number of "digits" base b needed to represent the number n. For example, log2(1000) = 9.9658, meaning that ten bits (base 2 digits) can represent any number less than 1000. In fact, the number log2(10) = 3.3219 gives the number of base 2 digits needed to represent 10, so it makes sense that log2(1000) = log2(103) = 3*log2(10) = 3*3.3219 = 9.9658. Similarly, a binary (base 2) number with 32 digits (that is, bits) will require 32*log10(2) = 32*0.30103 = 9.633 (or effectively 10) decimal digits for the same accuracy.

As another example, suppose instead of base 10, one uses base 20, with twice as many types of digits as with base 10. One might think this would mean that numbers are half as long, but this isn't true. The equation log20(100000) = 3.843 shows that for numbers less than 100000, instead of 5 base 10 digits, we would still need 4 base 20 digits -- not much of an improvement. Starting with a base b, in order to get numbers half as long, one would need to use b2 as the number base. So instead of base 10, one would need to use base 100 for the numbers to be half as long. This makes sense: any single base 100 digit represents 2 base 10 digits.

  x  to base  b  has  n  digits implies
  x  to base  10  needs  n*log10(b)  digits.

The same statement above is true with any other base in place of 10, so in particular:

  x  to base  10  has  n  digits implies
  x  to base  b needs
n*logb(10) = n/log10(b) digits.

A number with 25 decimal digits written base 20 would need 25*log20(10) = 15/log10(20) = 15/1.30103 = 19.215 (or effectively 20) base 20 digits for the same accuracy. A number with 20 decimal digits written base 16 would need 20*log16(10) = 20/log10(16) = 20/1.204 = 16.610 (or effectively 17) base 16 digits for the same accuracy.

To carry out pesky log calculations in some more limited programming languages you can use (but Python has this built-in as math.log(x, base) ):

  logb(x) = (1/log10(b))*log10(x) 

Or instead of logs base 10 above, you could use natural logs.

Here is a class Base that will convert back and forth. I first show it with code to apply it to the Gregory's series calculations.

Inside convert, the algorithm takes care of the integer part separately (function dbase), by processing left to right, picking off each digit and dividing by the base b. To the right of the decimal, the algorithm multiplies repeatedly by b to keep getting the next digit value. The function restore processes each digit in turn, left to right, adds in its value and multiplies by the base b. After the decimal point, it changes to dividing each time by b.

File: base.py, class: Base
base.py Rest of base.py Calculate Gregory's series
# base.py: convert to and from any base
# convert:
#    Input: int or double or mpmath,
#        the base, and no of decimals
#    Output: a str representing the num
# restore:
#    Input: str rep a fp num and base b
#    Output: the num as an internal int
#        or double or mpmath

ds = ['0','1','2','3','4','5','6','7','8',
  '9','a','b','c','d','e','f','g','h','i',
  'j','k','l','m','n','o','p','q','r','s',
  't','u','v','w','x','y','z',
  'A','B','C','D','E','F','G','H','I','J',
  'K','L','M','N','O','P','Q','R','S','T',
  'U','V','W','X','Y','Z']

dr = {'0':0, '1':1, '2':2, '3':3, '4':4, 
  '5':5,  '6':6,  '7':7,  '8':8,  '9':9, 
  'a':10, 'b':11, 'c':12, 'd':13, 'e':14, 
  'f':15, 'g':16, 'h':17, 'i':18, 'j':19, 
  'k':20, 'l':21, 'm':22, 'n':23, 'o':24, 
  'p':25, 'q':26, 'r':27, 's':28, 't':29, 
  'u':30, 'v':31, 'w':32, 'x':33, 'y':34, 
  'z':35, 'A':36, 'B':37, 'C':38, 'D':39, 
  'E':40, 'F':41, 'G':42, 'H':43, 'I':44, 
  'J':45, 'K':46, 'L':47, 'M':48, 'N':49, 
  'O':50, 'P':51, 'Q':52, 'R':53, 'S':54, 
  'T':55, 'U':56, 'V':57, 'W':58, 'X':59, 
  'Y':60, 'Z':61,}

def dbase(y, b): # int part >= 1
    s = ""
    while y > 0:
        s = ds[y%b] + s
        y = y // b
    return s
class Base: # number base convert
    def __init__(self):
        pass
    def convert(self,x,b=10,lim=8):
        if not(1 < b < 63):
            return "?"
        s = dbase(int(x),b)
        if  s == "": # int part
            s = '0'
        if x - int(x) == 0:
            return s
        s += '.'
        x -= int(x)
        d = 0
        for k in range(0,lim):
            x = (x - d)*b
            d = int(x)
            s += ds[d]
        return s

    def restore(self, st, b):
        i = 0;
        res = 0
        while True:
            if i == len(st):
                return res
            c = st[i]
            if c == '.':
                break
            res = res*b + dr[c]
            i = i + 1
        # here i --> '.' in st
        i = i + 1
        bs = b
        while i < len(st):
            res = (res +
              float(dr[st[i]])/bs)
            bs = bs * b
            i = i + 1
        return res
# mp.py: sum Gregory's series,
from __future__ import print_function
from mpmath import *
from base import *

mp.dps = 60 # digits of numbers
print("Prec:", mp.dps)
currB = 8 # base for answer
print("Base: ", currB)
currN = 8**5 # total terms in series

sum = mpf(0)
sign = 1
for k in range(0,currN):
    term = 1/(mpf(2)*k + 1)
    sum = sum + sign*term
    sign = -sign

print(k)
base = Base()
print(base.convert(sum*4, currB,
  int(mp.dps)))
print(base.convert(mp.pi, currB,
  int(mp.dps)))

  The class Base above is especially interesting to me because it works unchanged on doubles or on the extended floats of mpmath. When the function convert is invoked, the type of the first parameter, whether double or mpmath, determines the type of all the other float variables in the function, because when the actual parameter is combined with other variables, that forces these to take on the same type. The same is true for the function restore, where its second actual parameter (the base b) is made into an mpmath float.

The restore function in Base is not needed for the study below of Gregory's Series in other number bases. It is included for completeness.

The "float" at the end of the base.py file is there so that the code will execute properly in Python 2, since in Python 2, a single slash applied to 2 integers gives truncated integer division.

Here is a short test of Base using ordinary doubles.

   Test of convert and restore functions
# base.test.py: test restore in base
from __future__ import print_function
from base import *

base = Base()

def test(s, b):
    r = base.convert(s, b)
    print("Input:  %1.8f  ---convert(base10 to base%i) to---> Output: %c%s%c" %
          (s,b,"\"",r,"\"") )
    sr = base.restore(r, b)
    print("Input: %c%s%c ---restore(base%i to base10) to---> Output: %1.8f" %
          ("\"",r,"\"", b, sr) )
    print()

test(251.171875, 8)
test(3.1415926535, 8)
test(3.1415926535, 20)
test(3.1415926535, 7)
Input:  251.17187500  ---convert(base10 to base8) to---> Output: "373.13000000"
Input: "373.13000000" ---restore(base8 to base10) to---> Output:  251.17187500

Input:  3.14159265  ---convert(base10 to base8) to---> Output: "3.11037552"
Input: "3.11037552" ---restore(base8 to base10) to---> Output:  3.14159262

Input:  3.14159265  ---convert(base10 to base20) to---> Output: "3.2gceg9g9"
Input: "3.2gceg9g9" ---restore(base20 to base10) to---> Output:  3.14159265

Input:  3.14159265  ---convert(base10 to base7) to---> Output: "3.06636514"
Input: "3.06636514" ---restore(base7 to base10) to---> Output:  3.14159257

Here is a short program invoking the functions convert and restore using also the mpmath class. Notice that the first actual parameter of convert and the second actual parameter of restore are mpmath floats. In the output, all numbers represent pi to some base, and are 49 or 50 digits long after the decimal number, except for the 24 digit base 62 ones. All digits are correct except for those written in red.

   Test of convert and restore functions, using mpmath
# mp.test.py: test conversions when using mpmath
from __future__ import print_function
from mpmath import *
from base import *
mp.dps = 50

base = Base()
testnum = 1

def test(s, b, dig):
    global testnum
    print("TEST NUMBER %i, BASE %2i" % (testnum, b) )
    r = base.convert(s, b, dig)
    print("Input: ", s)
    print("   ---convert--(base10 to base%i)--to--->" % b)
    print("Ouput: %c%s%c" % ("\"", r, "\""))
    print()
    q = base.restore(r, mpf(b))
    print("Input: %c%s%c" % ("\"", r, "\"") )
    print("   ---restore--(base%i to base10)--to--->" % b)
    print("Output:", end=" ")
    print(q)
    print()
    testnum += 1

test(mp.pi,  7, 50)
test(mp.pi, 12, 50)
test(mp.pi, 62, 25)
test(mp.pi, 20, 50)
TEST NUMBER 1, BASE  7
Input:  3.1415926535897932384626433832795028841971693993751 (base 10)
   ---convert--(base10 to base7)--to--->
Ouput: "3.06636514320361341102634022446522266435206502401554" (base7)

Input: "3.06636514320361341102634022446522266435206502401554" (base7) 
   ---restore--(base7 to base10)--to--->
Output: 3.1415926535897932384626433832795028841971690439411 (base10)

TEST NUMBER 2, BASE 12 Input: 3.1415926535897932384626433832795028841971693993751 (base10) ---convert--(base10 to base12)--to---> Ouput: "3.184809493b918664573a6211bb151551a05729290a7809ab1a" (base12) Input: "3.184809493b918664573a6211bb151551a05729290a7809ab1a" (base12) ---restore--(base12 to base10)--to---> Output: 3.1415926535897932384626433832795028841971693993751 (base10)
TEST NUMBER 3, BASE 62 Input: 3.1415926535897932384626433832795028841971693993751 (base10) ---convert--(base10 to base62)--to---> Ouput: "3.8MhuCIRz3G3AAx5bN156EbKFo" (base62) Input: "3.8MhuCIRz3G3AAx5bN156EbKFo" (base62) ---restore--(base62 to base10)--to---> Output: 3.1415926535897932384626433832795028841971693978989 (base10)
TEST NUMBER 4, BASE 20 Input: 3.1415926535897932384626433832795028841971693993751 (base10) ---convert--(base10 to base20)--to---> Ouput: "3.2gceg9gbhj9d21hihb3egacb0361eb2bfb8h839j9ig6c30c8j" (base20) Input: "3.2gceg9gbhj9d21hihb3egacb0361eb2bfb8h839j9ig6c30c8j" (base20) ---restore--(base20 to base10)--to---> Output: 3.1415926535897932384626433832795028841971693993751 (base10) 1 2 3 4 5 123456789012345678901234567890123456789012345678901

Check the start of this page for a discussion of base conversions.

  • So in the second half of TEST 1, the 50 correct base 7 digits of pi, would yield 50*log10(7) = 50*0.8451 = 42.255 correct base 10 digits, and there are exactly 42 correct base 10 digits of pi in the final answer.

  • In the second half of TEST 2, the 47 correct base 12 digits of pi, would yield 47*log10(12) = 47*1.079 = 50.71 correct base 10 digits, which was enough to get back the 49 correct base10 digits displayed.

  • In TEST 3, the 25 correct base 62 digits of pi, would yield 25*log10(62) = 25*1.79 = 44.8 correct base 10 digits, and there are exactly 44 correct base 10 digits of pi in the final answer.

  • In TEST 4, the 39 correct base 12 digits of pi, would yield 39*log10(20) = 39*1.30103 = 50.74 correct base 10 digits, so again this is enough to get 49 correct base 10 digits of pi.

 


Gregory's Series for Other Bases: I had already seen experiments of the same sort in binary: bases 2, 4, 8, 16, etc. These bases are all essentially the same, just grouping the bits differently. Such experiments also display the same interesting property of revealing digits of pi, as illustrated with base 8 below: I also got digits of pi in the same way in bases 12 and 20, but not in a prime number base, illustrated with 3, 5 and 7, which only gives the first two sequences of digits of pi, along with the randomly occurring correct digits: in base b, a given digit will be correct 1/b of the time and two in a row will be correct 1/(b^2) of the time, and so forth.

If the base does not have 2 as a factor, then the expansion of one-fourth messes up all subsequent digits. This is because of the "pesky" 4's in the denominators of the terms of Euler's formula. This explains the behavior for bases other than 10, as shown below.

   Using other number bases: Bases 8 (and other binary ones), 12, and 20
Base 8, binary, same as bases 2, 4, 16, etc.
              3.1103755242102643021514230630505600670163211220111602105147630720020273724616611633104505120207461615002335737124315474647220615460126051557445742415647741
       8(8^1) 3.0105732144000672224301057321440006722243010
      64(8^2) 3.1003757241642737746355363136740316011712232
     512(8^3) 3.107375524410264042152373055526641342172463230726020207105570
    4095(8^4) 3.110275524210464302147023063145560061474321734555035621774573
   32768(8^5) 3.11036552421026450215142303705056007651632111446716021664242305754315242227167757
  262144(8^6) 3.11037452421026430235142306305032006701632207201116020317276307206330377246043527561050032203746505755523713272
 2097152(8^7) 3.11037542421026430215162306305056006441632112201125521051476306446002737246247372731045036743217616153004361273
16777216(8^8) 3.1103755142102643021514232630505600670162751220111602105244630720020273651376611633104566374607461614657747167124315772747406015457024126562741746715427734
Exact:pi:     3.1103755242102643021514230630505600670163211220111602105147630720020273724616611633104505120207461615002335737124315474647220615460126051557445742415647741

Base 12
  20736(12^4) 3.184709493b91b664573a2511bb160a81a0519a200ab94a46ba043a31796a5928a6
 248832(12^5) 3.1847b9493b918667573a6211b7451551a06680290a7774b402742509409a453703
2985984(12^6) 3.184808493b918664576a6211bb151514a05729290b7139a49274177017a762b398
Exact: pi     3.184809493b918664573a6211bb151551a05729290a7809a4927421406947728691

Base 20
   8000(20^3) 3.2gbeg9gbi49d21hccb3eh9dg030da9he4i3ccf9b5h7405b00jfig7f
 160000(20^4) 3.2gcdg9gbhj9d71hihb3ea5cb0362dc7bfb8bj11211j5ih96dgf5172
3200000(20^5) 3.2gcef9gbhj9d21i3hb3egacajh11eb2bfb9g98987debbgh6fcfagahjh7c77c22idag0b8f91ef39d2
Exact: pi     3.2gceg9gbhj9d21hihb3egacb0361eb2bfb8h83987debh5180cfag88d2c627c3fiacdi7ddid6ec0d0
                                                                                                                   1         1         1         1         1         1
                         1         2         3         4         5         6         7         8         9         0         1         2         3         4         5
                123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890

  Below are three examples of number bases without 2 as a factor. Notice that in each case there is only one section of extra digits of pi, except for randomly occurring ones. That section exists because in Euler's formula, the first term after the sum only has an N in the denominator. All other terms have a non-zero power of 4 in the denominator.

   Using other number bases: bases 3, 5, and 7.
Base 5
  78125(5^7)  3.03232224303343241124111243024120310132203321131203410330423434010300041440133
 390625(5^8)  3.03232220303343241124122343024120310003142210020102140041244420033343103123431
1953125(5^9)  3.03232214403343241124122404124120310003140414403441023431122341120441311424301
Exact: pi     3.03232214303343241124122404140231421114302031002200344413221101040332134400432

Base 7
 16807(7^5)   3.06640514320361322621115543052552601520551404054352101525155566662120502005642
117649(7^6)   3.06636614320361341054452204265025330041240010636044515355243466066532613042300
823543(7^7)   3.06636524320361341102615540631340451465512533004605255603302545301042552661561
Exact:pi:     3.06636514320361341102634022446522266435206502401554432154264310251611545652200

Base 3
59048#^10)   10.01021102002201021100211111022120201102022212121211021010200022201110102001121
Exact: pi    10.01021101222201021100211111022122222011120121212120012110010010122202221201201

                         1         2         3         4         5         6         7         8
                12345678901234567890123456789012345678901234567890123456789012345678901234567890

  (Revision date: 2018-10-08. Please use ISO 8601, the International Standard.)