Welcome, guest | Sign In | My Account | Store | Cart
4

This is a traditional base converter with the twist that it accepts any strings as the digits for the input and output bases.

Besides all the normal base-converts, you can now create compact versions of huge numbers by converting them to a base that uses all the letters and numbers for its digits, for example.

Python, 74 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
BASE2 = "01"
BASE10 = "0123456789"
BASE16 = "0123456789ABCDEF"
BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"

def baseconvert(number,fromdigits,todigits):
    """ converts a "number" between two bases of arbitrary digits

    The input number is assumed to be a string of digits from the
    fromdigits string (which is in order of smallest to largest
    digit). The return value is a string of elements from todigits
    (ordered in the same way). The input and output bases are
    determined from the lengths of the digit strings. Negative 
    signs are passed through.

    decimal to binary
    >>> baseconvert(555,BASE10,BASE2)
    '1000101011'

    binary to decimal
    >>> baseconvert('1000101011',BASE2,BASE10)
    '555'

    integer interpreted as binary and converted to decimal (!)
    >>> baseconvert(1000101011,BASE2,BASE10)
    '555'

    base10 to base4
    >>> baseconvert(99,BASE10,"0123")
    '1203'

    base4 to base5 (with alphabetic digits)
    >>> baseconvert(1203,"0123","abcde")
    'dee'

    base5, alpha digits back to base 10
    >>> baseconvert('dee',"abcde",BASE10)
    '99'

    decimal to a base that uses A-Z0-9a-z for its digits
    >>> baseconvert(257938572394L,BASE10,BASE62)
    'E78Lxik'

    ..convert back
    >>> baseconvert('E78Lxik',BASE62,BASE10)
    '257938572394'

    binary to a base with words for digits (the function cannot convert this back)
    >>> baseconvert('1101',BASE2,('Zero','One'))
    'OneOneZeroOne'

    """

    if str(number)[0]=='-':
        number = str(number)[1:]
        neg=1
    else:
        neg=0

    # make an integer out of the number
    x=long(0)
    for digit in str(number):
       x = x*len(fromdigits) + fromdigits.index(digit)
    
    # create the result in base 'len(todigits)'
    res=""
    while x>0:
        digit = x % len(todigits)
        res = todigits[digit] + res
        x /= len(todigits)
    if neg:
        res = "-"+res

    return res
    

Motivation and application

I needed a system to include a reference number in an outgoing email, so that a reply email could be tracked to the original context. I'm betting that words in the subject line are the most likely to be returned in a reply email (as opposed to words in the body or headers), so I sought a way to compress/obfuscate my reference number into a word that would comfortably fit in the subject line. Now instead of 20020111152908/20020129120001, I can simply include "H6Xwq/IjmAf". It's shorter, less prone to tampering, and it looks more like a magic cookie than some MTA junk. In fact, I could boost the security even more by privately shuffling the order in which I use the letters as digits (instead of A==0, B==1, etc).

Other uses

I didn't find a base converter in python upon doing some quick searches, so perhaps this code would be useful for everyday conversions as well.

8 comments

pc451 14 years, 1 month ago  # | flag

Does not handle non-zero systems. This gives an incorrect answer for non-zero base systems. For instance, if BASE8=12345678 then decimal '9' should be octal '11'. Instead, it's given as '21'.

Drew Perttula (author) 13 years, 10 months ago  # | flag

confusion about "non-zero systems" The digits for BASE8 would be "01234567", not "12345678". With the latter set of digits, decimal 9 is properly output as '22' (octal, but not with the standard digits). The standard octal for decimal '9' is '11'.

Note that baseconvert("9","123456789","12345678") will output '21', but that input is not a decimal 9. It's a decimal 8.

Bojan Zagar 13 years, 7 months ago  # | flag

What abut OCT ? Python 2.3:

>>> baseconvert(9,"01234567890","01234567")

'11'

this is ok, but

than:

>>> baseconvert(10,"01234567890","01234567")

'13'

I thin, it should be 12 ??

Best regards.

Bojan Zagar 13 years, 7 months ago  # | flag

Sorry. the BASE10 was wrong :)))

s g 12 years, 5 months ago  # | flag

further confirmation. This problem appears to have something to do with the number of digits in the base10 number being converted.

In [28]: baseconvert(99,"01234567890","01234567") Out[28]: '154'

In [29]: baseconvert(100,"01234567890","01234567") Out[29]: '171'

LesPaul 12 years, 2 months ago  # | flag

0 (Zero) does not work? Been just trying baseconvert(0, BASE10, BASE62), it simply returns null string. A simple solution is to add:

if x == 0:
    res = todigits[0]

just below the res="" line for the correct operation.

Chris Smith 11 years, 2 months ago  # | flag

you can inter-convert with Word-representations. With a little re help, you can convert back from a word representation like "OneZeroOne". I believe this will work as long as you use words that start with a capital as you have done. The approach is to use an alternation regex expression to split the number into "digits" and then continue as you already do.

At the start of the def, put an "import re" statement and then replace the "for digit in str(number)" with the following:

fromdigits=list(fromdigits)
pat=fromdigits[:]
pat.sort(lambda x,y:cmp(len(y),len(x))) #longest first
pat="(%s)" % '|'.join(pat)
number=[tmp for tmp in re.split(pat,str(number)) if tmp!='']
for digit in number:

Here is a demonstration:

###
>>> BASEWORDS=('Zero','One')
>>> BASE2='01'
>>> baseconvert('OneZeroOne',BASEWORDS,BASE2)
'101'
>>>
###
Ryan 8 years, 2 months ago  # | flag

I have posted the Py3K compatible code with LesPaul's suggested change to pastebin at http://pastebin.com/f54dd69d6

Add a comment

Sign in to comment

Created by Drew Perttula on Thu, 31 Jan 2002 (PSF)
Python recipes (4552)
Drew Perttula's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks