Number bases part 8: class R Rebaser

Here is my class R rebaser. I’ve named it such as it can handle real numbers, both as bases and as inputs. If you only ever intend to use integers for both bases and inputs, it’s better to use the class Z rebaser instead; it’s much faster.

It’s run similar to class Z, you initialize it first and then call its Rebase function:

>>> b=RBaser()
>>> b.Rebase(3.141592653589793,10,2)
'11.0010010000111111011010101000100010000101110111010011101110010000001111010000111100101011000111000'

You can also pass it numeric strings of an arbitrary length to be more precise. Here is pi in base e:

>>> b.Rebase('3.1415926535897932384626433832795', 10, '2.71828182845904523536')
'10.1010020200021111200201011200010102020001110120200210100212012000202'

But passing strings of such length doesn’t insure good precision. Converting back one gets:

>>> b.Rebase('10.1010020200021111200201011200010102020001110120200210100212012000202', '2.71828182845904523536', 10)
'3.14159265358979323846264338327'

The class uses the decimal module to be able to convert numbers precisely, but you’ll have to set the precision through the class by using its .prec function:

>>> b.prec(85)
85
>>> b.Rebase('3.1415926535897932384626433832795', 10, '2.71828182845904523536')
'10.1010020200021111200201011200010102020001110120200210100212012000202120110012120102010112110112120001021120010001102011112020102020211002100200120000201111101001010200201210011020010020011020212'

And converting back its:

>>> b.Rebase('10.1010020200021111200201011200010102020001110120200210100212012000202120110012120102010112110112120001021120010001102011112020102020211002100200120000201111101001010200201210011020010020011020212', '2.71828182845904523536', 10)
'3.141592653589793238462643383279499999999999999999999999999999999999999999999999999999'

It’s more precise, but there’s still the rounding issue at the very end there which can be handled by being more precise with the inputs.

You may copy and paste the code from below or download the .py file from here.

import warnings
#the warnings should always appear ('cause they're rare!)
warnings.simplefilter("always")                                       
####warnings.simplefilter("ignore")


try:#try using mpmath if installed
    from mpmath import mp as _mp
    _Deci=_mp.mpf;_log=_mp.log
    _using='mpmath'

except:#if not, use the built-in modules
    from math import log as _log
    from decimal import getcontext as _getCon
    from decimal import Decimal as _Deci
    _using='decimal'

##except:#don't use the decimal module (not recommended)
##    from math import log as _log
##    def _Deci(n):return float(n)
##    _using=None

class RBaser:
    '''A number rebasing system from Orthallelous
        Class R

        Can convert a number of any base of any real number(R).

        Normal acceptable bases are >-1 or 1<
            If a base requires a character that unicode does not have,
             then the system returns a list containing the
             positional values in base 10.

        Bases 1, 0 and -1 are special cases:
            Base 1 returns a string the length of the number.
            Base -1 is similar to base 1, but follows how positive
             and negative numbers behave in negative bases.
            These bases are not considered to be normal acceptable bases.

        Bases between -1 and 1 but not 1, 0 or -1 are fractional bases
         These bases use characters that correspond to their inverted base.
        '''
    def __init__(self):
        from sys import maxunicode
        
        self._maxChr=maxunicode+1#base 0 uses no characters
        #so bases 1 to 65536 are valid (instead of 0 to 65535)(or 1 to 1114112)

        self._InitString()#build intial string
        self._prec=30#precision
        self._Base1_chr=self._str[0]#char for bases 1, -1
        self._spec_case=True#enable/disable special case bases
        self._neg='-'#internal neg and sep signs
        self._sep='.'
        self.Class='R'

        #---- error related code -----
        class RidiculousError(ValueError):pass
        class BaseZero(ValueError):pass
        self._BZErr=BaseZero
        self._RidErr=RidiculousError
        self._MaxLengthAllowed=100000#max allowed string length

        self._rebuildDicts()

#--- Text display/print system ---------------------------------------------------------------------
    def _Verify(self,Print=False):
        'Determines if every character currently in ._str appears only once.'
        #~19 minutes for len(str)=1114112(in which there are 1048576 repeats)
        if Print:print('Verifying...')
        Str=self._str[::-1]#remove the last occurance of the repeat chars (if found)
        rp=len(Str)-len(set(Str))
        if rp:
            if Print:
             print('Mismatch!\nFound {:,} Repeated character{}\nRepairing...'.format(rp,'s'if rp>1 else''))
            for u in range(self._maxChr):
                ch=unichr(u)
                if Str.count(ch)<2:continue
                while Str.count(ch)>1:Str=Str.replace(ch,'',1)
            self._str=Str[::-1]
            if Print:print('Rebuilding Dictionaries...')
            self._rebuildDicts();self._Verify(Print)
        else:
            if Print:print('No repeated characters, verification complete.')
            return
#--- string constructor ----------------
    def _InitString(self):
        'constructs the inital string'    
        R,L=[],'''0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz$&#?!@:;<=>[\]"%'()*+,-./^_`{|}~ \t\n\r'''
        for j in range(255,-1,-1):
            if unichr(j)not in L:R.append(unichr(j))
        self._str=''.join([L]+R)# get remaining characters

    def _Constructor(self,B,invert=False):
        'builds the string used for displaying the numbers'
        rest,B=[],int(abs(B))

        if B>len(self._str):#if the base is larger than the string, add to it
            b=B
            if B>=self._maxChr:b=self._maxChr-1# handle last unicode character cleanly
            rest.extend(map(unichr,range(len(self._str),b+1)))

            self._str=''.join([self._str,''.join(rest)])#add to string permanently
            self._rebuildDicts()

        #note: the Constructor! (raise your fist and have it emanate light while you say it)
        #       should always accept the base number and the bool invert
        #       and have the two return lines below. The rest one can strip out
        #       and replace with whatever builds the string in whatever order you'd like
        
        if invert:return self._DictB#{1:'1'} -value:character
        else:return self._DictF#{'1':1}      -character:value
#--- end string constructor ------------

    def _rebuildDicts(self):
        'rebuilds the dicionaries storing the character:value data'
        self._DictB=dict(enumerate(self._str))#{1:'1'}# value:character
        self._DictF=dict(zip(self._str,range(len(self._str))))#{'1':1}# character:value

    def _displayer(self,L,B,neg='-',sep='.'):
        '''Converts a number list L of base B into a string
        If abs(B) is higher than unicode, then it just returns the list'''
        _n,_s=self._neg,self._sep
        #list contains a negative number but the sign isn't in order(should always be first)
        if _n in L and L[0]!=_n:L.remove(_n);L.insert(0,_n)

        Max=abs(max([i for i in L if type(i)in[int,long]]))
        #a value is in the list that is equal or greater than the base (it shouldn't be there)
        if Max>=abs(B)or None in L:
         E=TypeError(u'Unsupported value for base {}: {}'.format(B,Max))
         raise E

        #list contains value higher than maxunicode, return as a list (past the error above, so its fine)
        if Max>=self._maxChr:return L

        #if all the numbers after the sep are zero, lop them off
        if _s in L:
            if not any(L[L.index(_s)+1:]):L=L[:L.index(_s)]

        syms,n,f=self._Constructor(abs(B),True),'',''#{1:'1'}
        syms[_n]=neg;syms[_s]=sep;n=map(syms.get,L)
        return''.join(n)
    
    def _deplayer(self,num,B,neg='-',sep='.'):
        '''Converts a string (num) of base B into a list in decimal.
        Its the opposite of _displayer. Only works with bases,
         abs(B), that are smaller than unicode'''
        _n,_s=self._neg,self._sep
        syms,N=self._Constructor(abs(B)),[]#{'1':1}
        
##        try:num=_Deci(num)#for really small numbers
##        except:pass#if you pass this something like 0.00002, python sees it as
##                   # 2e-05 and when it strings it, it becomes '2e-05', not '0.00002'
##                   # and that causes problems below
            
        num=unicode(num)
        if neg in num:N=[_n];num=num.replace(neg,'')
        if sep in num:#if string gets here, sep should appear only once
            Num,Ber=num.split(sep)
            N.extend(map(syms.get,Num))
            N.append(_s)
            N.extend(map(syms.get,Ber))
            #num=num.replace(sep,'.');syms[sep]='.'

        else:N.extend(map(syms.get,num))
        #a character was used that has a value equal or greater than the base (it shouldn't be there)
        Max=abs(max([i for i in N if type(i)in[int,long]]))

        if Max>=abs(B)or None in N:
          ch=self._DictB.get(Max)#if there's a None, then the dict doesn't have a character for that value, much less know if its higher than B
          E=TypeError(u'Unsupported character for base {}: {}'.format(B,ch))
          raise E

        return N
#--- END Text display/print system -----------------------------------------------------------------

#--- special case bases-----------------------------------------------------------------------------    
#--- special case base -1 --------------
    def _10_to_Neg1(self,n):
        'Converts a number in base 10 to base -1'
        if n>0:return self._Base1_chr*(n*2-1)
        if not n:return''
        if n<0:return self._Base1_chr*(-n*2)

    def _Neg1_to_10(self,n):
        'Converts a string of a number in base -1 to base 10'
        l=len(n)
        if not l%2:return-l/2
        else:return l/2+1
#--- end special case base -1 ----------

#--- special case base 0 ---------------
    def _Zero_to_10(self,n):
        'Converts a number in base 0 to base 10'
        E1=TypeError("A base zero number system doesn't have any characters")
        E2=self._BZErr("Indeed. Makes you think, doesn't it?")
        if not n:
         raise E2
        else:
         raise E1
#--- end special case base 0 -----------        

#--- special case base 1 ---------------
    def _10_to_1(self,n):
        'Converts a number in base 10 to base 1'
        if n<0:return''.join([self._neg,self._Base1_chr*abs(int(n))])
        elif n>0: return self._Base1_chr*int(n)
        else:return''

    def _1_to_10(self,n):
        'Converts a string of a number in base 1 to base 10'
        if self._neg in n:return -(len(n)-1)
        else: return len(n)
#--- end special case base 1 -----------

#--- bases between -1 and 1 ------------
        # to use, first convert into base 1/b, this call this
    def _10_to_Frac_B(self,n,B):
        'Converts a number from base 10 to base B Where -1<B<1 and B!=0'
        _n,_s=self._neg,self._sep
        
        if not -1<B<1 or B==0:
         E="this function does not accept a base of this value: {:.4f}".format(B)
         raise TypeError(E)
        
        invert=_Deci(1)/_Deci(B)
        if B<0:L=self._10_to_NB(n,invert)
        else:L=self._10_to_B(n,invert)
        if L==[0]:return L

        add_neg=False
        if _n in L:L.remove(_n);add_neg=True
        shift=L.index(_s)-1
        # for a base 0<b<1, convert to base 1/b, shift fractional separator to the left one digit, swap all the digits, see ref. 2
        L.remove(_s);L.insert(shift,_s)
        if add_neg:L.append(_n)
        L.reverse()
        #remove any leading zeros
        while L[0]==0:
            if L[1]==_s:break#leave at least one leading zero in front of the fractional separator
            L=L[1:]
        return L
#--- end bases between -1 and 1 --------
#--- END special case bases-------------------------------------------------------------------------

#--- Normal acceptable base systems ----------------------------------------------------------------
#--- bases <-1 -------------------------
    def _10_to_NB(self,n,B):
        '''Converts a number from base 10 to base b (<-1). returns a list
        see ref. 8 for handling negative real bases'''
        if n==0:return[0]
        #these have to use the decimal module for precise conversion
        n=_Deci(n);b=_Deci(abs(B))
        d=_Deci(int(_log(abs(n),b)))
        
        prec=self._prec;D=[]#precision, digits
        l,r,X=-b/(b+1),1/(b+1),n
        
        def T(x):return-b*x-int(-b*x-l)
        
        if d<=self._MaxLengthAllowed:#don't do this if d is already this big
         while not l<=X<r:d+=1;X=n/pow(-b,d)# d is better determined here, the log taken above just speeds it up

        if d<=0:#starting with a fractional value
            D.extend([0,self._sep])
            #D.extend(([0]*-int(d)))

        if d>self._MaxLengthAllowed:#don't deal with values this huge
            E1='not going to make a string that is at least this length: {:,}'.format(int(d))
            E=self._RidErr(E1)
            raise E

        #this prec in new base appears to work for negative bases so I'm going with it
        prec=int(prec*_log(10)/_log(abs(B)))#insure precision is in new base
        prec=d-prec#go only as far as precision is good
        if prec>=0:prec=-1#but in this event, go as far as B^-1

        while d>=prec:
            if d==0 and self._sep not in D:D.append(self._sep)#don't add it if its already there!
            xi=int(-b*X-l);X=T(X);d-=1
            if xi==b:D.extend([xi-1,0])#not that it should ever give this result, but if so, you do this.
            else:D.append(xi)
        return D
#--- end bases <-1 ---------------------

#--- bases >1 --------------------------
    def _10_to_B(self,n,B):
        'Converts a number in base 10 to base b (>1). Returns a list'
        if n==0:return[0]
        
        prec=self._prec;D=[]#precision, digits

        if n<0:D=[self._neg];n*=-1#negatives

        n=_Deci(n);B=_Deci(B)#make sure all values are arbi. prec. form
        P=_Deci(int(_log(abs(n),B))+1.)#get log power
        X=n/pow(B,P)

        #while not 0<=X<1:
        #    P+=1;X=n/pow(B,P)
            
        if P<=0:#starting with a fractional value
            D.extend([0,self._sep])
            D.extend(([0]*-int(P)))

        def T(x):return B*x-int(B*x)

        if P>self._MaxLengthAllowed:
            E=self._RidErr('not going to make a string that is at least this length: {:,}'.format(int(P)))
            raise E

        prec=int(prec*_log(10)/_log(B))#insure precision is in new base
        prec=P-prec#go only as far as precision is good
        if prec>=0:prec=-1#but in this event, go as far as B^-1

        while P>prec:
            a=int(X*B);X=T(X)
            if P==0 and self._sep not in D:D.append(self._sep)#don't add it if its already there!
            D.append(a);P-=1

        return D
#--- end bases >1 ----------------------

#--- definition of base ----------------
    def _B_to_10(self,n,B):
        'Converts a list of numbers(n) from ANY base to base 10'
        s,N=1,0;B=_Deci(B)
        if self._neg in n:n.remove(self._neg);s=-1#handle negatives
        
        #determine order of magnitude 
        try:P=_Deci(n.index(self._sep)-1);n.remove(self._sep)# floats
        except:P=_Deci(len(n)-1)# ints, longs

        for j in n:N+=j*pow(B,P);P-=1
        return s*N
#--- end definition of base ------------
#--- END Normal acceptable base systems ------------------------------------------------------------

#--- Main functions --------------------------------------------------------------------------------
    def __call__(self, num, B1, B2, neg='-', sep='.'):
        return self.Rebase(num, B1, B2, neg, sep)
    
    def Rebase(self, num, B1, B2, neg='-', sep='.'):
        '''Convert a number(can be a string) from base B1 to base B2

        num can be an int, long, float, string, Decimal or a list of postional values
        B1 and B2 can be ints, longs, floats, Decimal or numeric strings

        Set the fractional seperating sign (sep) to a character that
         wouldn't show up as a numerical value in the base.

        Set the negative sign (neg) to a character that
         wouldn't show up as a numerical value in the base.

        A fractional separator that shows up more than once in the
         given number implies that it might already bein use as a
         numerical value. Likewise for the negative sign.

        The negative sign must be leading the number.
        '''

        # if the number is zero, why do any math? return a zero
        if not num:
            #special case bases handle zero differently
            if-1<=B1<=1 or-1<=B2<=1:pass
            else:return 0

        #--------error handling---------------------------------------
        E1=ValueError('Negative sign appears more than once')
        E2=ValueError('Negative sign is not leading the number')
        E3=ValueError('Fractional separator appears more than once')
        l=[str,unicode]
        if type(num)in l:
            if num.count(neg)>1:
                raise E1
            if neg in num and num[0]!=neg:
                raise E2
            if num.count(sep)>1:
                raise E3

            #not an error check, see if the string is a zero
            zeroQues=num.replace(neg,'').replace(sep,'')
            if zeroQues.count(self._str[0])==len(zeroQues) and len(zeroQues):
                return 0

        if type(B1)in l:
            try:B1=_Deci(B1)#see if its a numeric string, if not raise error
            except:
                V1=ValueError(u"'{}' is not a valid base".format(B1))
                raise V1
        if type(B2)in l:
            try:B2=_Deci(B2)
            except:
                V2=ValueError(u"'{}' is not a valid base".format(B2))
                raise V2
            
        if abs(B1)in[0,1]or abs(B2)in[0,1]:
            if not self._spec_case:
                return'Special case bases are disabled'
        
        #N0 - number converted from input to list
        #N1 - number converted from base B1 to base ten(in list)
        #N2 - number converted from base ten(in list) to base B2(in list)
        #val- number converted from base B2(in list) to output

        #handle bases -1, 0, 1: starting base ------------------------
        if B1 in[-1,0,1]:
            if B1==-1:N1=self._Neg1_to_10(num)
            if B1==0:N1=self._Zero_to_10(num)
            if B1==1:N1=self._1_to_10(num)

        else:#handle all other starting bases
            if type(num)==list:
                N1=self._B_to_10(num,B1)
            else:#all other bases
                b1=B1
                if -1<B1<1:b1=_Deci(1.)/B1#so it doesn't break the deplayer
                N0=self._deplayer(num,b1,neg,sep)
                N1=self._B_to_10(N0,B1)

        #handle bases -1, 0, 1: ending bases--------------------------
        if B2 in[-1,0,1]:
            if B2==-1:return self._10_to_Neg1(N1)
            if B2==0:return''
            if B2==1:return self._10_to_1(N1)
            
        else:#handle all the other ending bases
            if B2<-1:N2=self._10_to_NB(N1,B2)
            elif B2>1:N2=self._10_to_B(N1,B2)
            else:#bases -1<B2<1 and not 1,0 or -1
                N2=self._10_to_Frac_B(N1,B2)
                B2=_Deci(1)/_Deci(B2)#for not breaking the displayer in the next line

        #put the converted value into a readable format
        val=self._displayer(N2,B2,neg,sep)

        try:#val could have repeated neg and sep after base conversion
            if val.count(neg)>1:# check for that and give a warning.
                W1=E1.args[0]
                warnings.warn(W1)
            if val.count(sep)>1:
                W2=E3.args[0]
                warnings.warn(W2)
        except:pass

        #return the converted value-----------------------------------
        if type(val)==list:return val#if its a list here, then it should return as one
        try:return int(val)
        except:
            try:return str(val)
            except:return unicode(val)

    def InDecimal(self, num, B, neg='-', sep='.', as_str=False):
        """Leaves num in base B, but converts each symbol to its value in base 10
        Will return a list unless as_str is True"""
        num=unicode(num)

        Dec=self._deplayer(num,_Deci(B),neg,sep)
        
        if as_str:
            dec=[];split=':;|'# this split is just in case the negative sign or fractional sign takes this symbol
            split=split.replace(neg,'');split=split.replace(sep,'')
            
            #if the number has both whole and fractional parts, 5.6 should be one block, not 5:.:6
            if self._sep in Dec:
                loc=Dec.index(self._sep);Dec.pop(loc)
                whol=Dec.pop(loc-1);frac=Dec.pop(loc-1)
                Dec.insert(loc-1,sep.join([str(whol),str(frac)]))

            #if the number is negative, it should be -num, not -:num
            if self._neg in Dec:
                Dec.pop(0);num=Dec.pop(0)
                Dec.insert(0,''.join([neg,str(num)]))
    
            for j in Dec:dec.append(str(j))
            Dec=split[0].join(dec)
        return Dec

    def Guesser(self, Num, B=10, neg='-', sep='.'):
        '''Attempts to guess the base of Num and convert it to base B
        Warning: The found base is the minimum base required for the
         characters in Num. The actual base may be higher or
         the negative of what is found. [ actual >= abs(found) ]
                 
        This function may also take some time'''

        num=unicode(Num)
        num=num.replace(neg,'')#replace these as they 
        num=num.replace(sep,'')# don't count as numerical characters
        
        Current=257
        step=1500#how many additional characters to add and search through at a time.
        syms=self._Constructor(Current)#{'1':1}

        LastIter=0#for ending the while loop if it ran out of characters (instead of throwing an unhelpful error)

        High=[]#highest value found
        reMove=[]#list of chars to remove from num
        while len(num):
            for char in num:#search for every character
                try:High.append(syms[char]);reMove.append(char)
                except:#found a character not currently on hand
                    Current+=step
                    syms=self._Constructor(Current)#get more characters

                    if Current >= self._maxChr:LastIter+=1#ready the last char error

                    if LastIter>1:#if == 1, may have found the last character but haven't removed it yet, so toss error when its 2 or more
                        E=ValueError(u"Unknown character: '{}'".format(num[0]))
                        #possibly hit a character not in the current encoding system
                        # but if that happens, then there'd be an error on the above line?
                        # I don't see how it can happen, but I've seen it happen, so... thus this check
                        raise E
                        
            for C in reMove:num=num.replace(C,'')#clear currently found characters
            reMove=[]#reset this

        Base=max(High)+1      
        #print("Possible base for given string: {}".format(Base))
        return Base, self.Rebase(Num,Base,B,neg,sep)

#--- END Main functions ----------------------------------------------------------------------------
#--- Other -----------------------------------------------------------------------------------------

    def prec(self, prec=None):
        'Sets the precision. Pass None for the current precision.'

        #note: precision is defined in base 10. As such it must be redefined in the
        #      base that is being used so as the number in the new base so it doesn't lose
        #      precision. That is, the number shouldn't return with trailing garbage (45.0000000000000452....)
        #      prec = log(X,10) -> prec = Ln(X)/Ln(10) -> e^prec*10 = X
        #      prec_in_base_B = ? -> ? = Ln(X)/Ln(B) -> ? = (prec*Ln(10))/Ln(B)
        #      thus prec_in_base_B = (prec*Ln(10))/Ln(B)

        if prec is None:
            return self._prec

        prec=abs(prec)
        if _using=='mpmath':#using mpmath
            _mp.dps=prec
        elif  _using==None:
            pass
        else:#using built-in decimal module
            _getCon().prec=prec
        self._prec=prec
        return prec

    def references(self):
        'Reference information'
        Refs={'Note':'Some references may not have made it into the final version.\n',
        1: 'Weisstein, Eric W. "Base." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/Base.html',
        2: "http://expertscolumn.com/content/how-convert-decimal-number-any-fractional-base-tutorial",
        3: "http://mathforum.org/library/drmath/view/71589.html",
        4: "http://code.activestate.com/recipes/81611-roman-numerals/",
        5: "http://mathematics.knoji.com/how-to-convert-a-decimal-number-into-negative-base-a-tutorial",
        6: "http://en.academic.ru/dic.nsf/enwiki/5844205",
        7: "'Negative Based Number Systems' W. J. Gilbert, R. J. Green, 'Mathematics Magazine' Vol. 52, pg. 240, 1979",
        8: "'Beta-expansions with negative bases' S. Ito, T. Sadahiro, 'Integers' Vol. 9, pg. 239, 2009"}

        R=sorted(Refs)
        for r in [R[-1]]+R[:-1]:print(' '.join([str(r).center(4)+' : '+Refs[r]]))
        print('\nThis  \nwork : https://orthallelous.wordpress.com/2014/06/28/number-bases-pt8/')
#---------------------------------------------------------------------------------------------------
#--- END CLASS -------------------------------------------------------------------------------------

Note: this code uses Python’s decimal module. But if you happen to have mpmath, it’ll use that instead.
 
 

So what can you do with this? You can use it to convert any real number between any integer base:

>>> b.Rebase(37.4453, -46, 12)
'-AB.1030B9703040A78802953A34143B7430478547BA988236524532A9644A217A89A7BA587BB465A361279731144A'

Or convert any integer number between any real base:

>>> b.Rebase(77, 82.78321, 3.3345)
'111102.11030021023012112121123000100002011010012210210301201211020302012101120003011113020002303100202022123100120002102022102000001001210002000120112102002300200022112023020010113021001213002'

Or even if you want, you can hide messages:

>>> b.Rebase("0.000000000000 I'm really small!", 97, 7)
'0.0000000000000000000000000000433546465550006433236341202663106450011115164261265540422211413466521613431354642132561333525263535356625122056152255'

>>> b.Rebase('0.171429246949113576245931485487408827188565505806152646272021169630125313515832684233932234173120435716932816', 10, 95)
'0.GREETINGS HUMAN! GREETINGS HUMAN! GREETINGS HUMAN'

———–
 
 
 
 
 
 
 
 

So now that every possible base has been covered, we’re done right? Well no, every possible real base has been covered. If negative number bases were deep waters, then complex bases are uncharted seas. And like uncharted territory, there be dragons…

Posted in Number Bases, Python

Leave a comment

In Archive
Design a site like this with WordPress.com
Get started