pythonnumbersradix

Convert a number to Excel’s base 26


OK, I'm stuck on something seemingly simple. I am trying to convert a number to base 26 (ie. 3 = C, 27 = AA, ect.). I am guessing my problem has to do with not having a 0 in the model? Not sure. But if you run the code, you will see that numbers 52, 104 and especially numbers around 676 are really weird. Can anyone give me a hint as to what I am not seeing? I will appreciate it. (just in case to avoid wasting your time, @ is ascii char 64, A is ascii char 65)

def toBase26(x):
    x = int(x)
    if x == 0:
        return '0'
    if x < 0:
        negative = True
        x = abs(x)
    else:
        negative = False
    def digit_value (val):
        return str(chr(int(val)+64))
    digits = 1
    base26 = ""
    while 26**digits < x:
        digits += 1
    while digits != 0:
        remainder = x%(26**(digits-1))
        base26 += digit_value((x-remainder)/(26**(digits-1)))
        x = remainder
        digits -= 1
    if negative:
        return '-'+base26
    else:
        return base26

import io    
with io.open('numbers.txt','w') as f:
    for i in range(1000):
        f.write('{} is {}\n'.format(i,toBase26(i)))

So, I found a temporary workaround by making a couple of changes to my function (the 2 if statements in the while loop). My columns are limited to 500 anyways, and the following change to the function seems to do the trick up to x = 676, so I am satisfied. However if any of you find a general solution for any x (may be my code may help), would be pretty cool!

def toBase26(x):
    x = int(x)
    if x == 0:
        return '0'
    if x < 0:
        negative = True
        x = abs(x)
    else:
        negative = False
    def digit_value (val):
        return str(chr(int(val)+64))
    digits = 1
    base26 = ""
    while 26**digits < x:
        digits += 1
    while digits != 0:
        remainder = x%(26**(digits-1))
        if remainder == 0:
            remainder += 26**(digits-1)
        if digits == 1:
            remainder -= 1
        base26 += digit_value((x-remainder)/(26**(digits-1)))
        x = remainder
        digits -= 1
    if negative:
        return '-'+base26
    else:
        return base26

Solution

  • The problem when converting to Excel’s “base 26” is that for Excel, a number ZZ is actually 26 * 26**1 + 26 * 26**0 = 702 while normal base 26 number systems would make a 1 * 26**2 + 1 * 26**1 + 0 * 26**0 = 702 (BBA) out of that. So we cannot use the usual ways here to convert these numbers.

    Instead, we have to roll our own divmod_excel function:

    def divmod_excel(n):
        a, b = divmod(n, 26)
        if b == 0:
            return a - 1, b + 26
        return a, b
    

    With that, we can create a to_excel function:

    import string
    def to_excel(num):
        chars = []
        while num > 0:
            num, d = divmod_excel(num)
            chars.append(string.ascii_uppercase[d - 1])
        return ''.join(reversed(chars))
    

    For the other direction, this is a bit simpler

    import string
    from functools import reduce
    def from_excel(chars):
        return reduce(lambda r, x: r * 26 + x + 1, map(string.ascii_uppercase.index, chars), 0)
    

    This set of functions does the right thing:

    >>> to_excel(26)
    'Z'
    >>> to_excel(27)
    'AA'
    >>> to_excel(702)
    'ZZ'
    >>> to_excel(703)
    'AAA'
    >>> from_excel('Z')
    26
    >>> from_excel('AA')
    27
    >>> from_excel('ZZ')
    702
    >>> from_excel('AAA')
    703
    

    And we can actually confirm that they work correctly opposite of each other by simply checking whether we can chain them to reproduce the original number:

    for i in range(100000):
        if from_excel(to_excel(i)) != i:
            print(i)
    # (prints nothing)