Python: Converting Numbers to Words

One of my first self-imposed projects at my job was to recreate a certain annoying application that was a Frankenstein monster: a Microsoft Access file with a VBA GUI. For the most part, the application didn't even have a database. Anyway, part of the application allowed the user to type in an amount for a check and the VBA code would magically translate the numbers into the text you would normally write on a check. For example, let's say I wrote a check for $1,234.56. It would translate that to "one thousand two hundred thirty four dollars and fifty six cents". I needed a way to do the same thing in Python!

I spent quite some time trying to come up with the correct word formula to put into Google that would return what I needed. Unfortunately, after much labor and a fair bit of frantic typing, I came up with nothing. I had found something that no one in Pythondom had ever done!!! Or my Google-fu was bad that day, but I want to think it was the former.

The VBA code had a clue that I no longer recall. I think it was the name of the VBA library that did the trick. Anyway, whatever it was led me to the following code (note: the link below no longer works):

#!/usr/bin/env python
'''Convert number to English words
$./num2eng.py 1411893848129211
one quadrillion, four hundred and eleven trillion, eight hundred and ninety 
three billion, eight hundred and forty eight million, one hundred and twenty 
nine thousand, two hundred and eleven
$

Algorithm from http://mini.net/tcl/591
'''

# modified to exclude the "and" between hundreds and tens - mld

__author__ = 'Miki Tebeka '
__version__ = '$Revision: 7281 $'

# $Source$

import math

# Tokens from 1000 and up
_PRONOUNCE = [ 
    'vigintillion',
    'novemdecillion',
    'octodecillion',
    'septendecillion',
    'sexdecillion',
    'quindecillion',
    'quattuordecillion',
    'tredecillion',
    'duodecillion',
    'undecillion',
    'decillion',
    'nonillion',
    'octillion',
    'septillion',
    'sextillion',
    'quintillion',
    'quadrillion',
    'trillion',
    'billion',
    'million ',
    'thousand ',
    ''
]

# Tokens up to 90
_SMALL = {
    '0' : '',
    '1' : 'one',
    '2' : 'two',
    '3' : 'three',
    '4' : 'four',
    '5' : 'five',
    '6' : 'six',
    '7' : 'seven',
    '8' : 'eight',
    '9' : 'nine',
    '10' : 'ten',
    '11' : 'eleven',
    '12' : 'twelve',
    '13' : 'thirteen',
    '14' : 'fourteen',
    '15' : 'fifteen',
    '16' : 'sixteen',
    '17' : 'seventeen',
    '18' : 'eighteen',
    '19' : 'nineteen',
    '20' : 'twenty',
    '30' : 'thirty',
    '40' : 'forty',
    '50' : 'fifty',
    '60' : 'sixty',
    '70' : 'seventy',
    '80' : 'eighty',
    '90' : 'ninety'
}

def get_num(num):
    '''Get token <= 90, return '' if not matched'''
    return _SMALL.get(num, '')

def triplets(l):
    '''Split list to triplets. Pad last one with '' if needed'''
    res = []
    for i in range(int(math.ceil(len(l) / 3.0))):
        sect = l[i * 3 : (i + 1) * 3]
        if len(sect) < 3: # Pad last section
            sect += [''] * (3 - len(sect))
        res.append(sect)
    return res

def norm_num(num):
    """Normelize number (remove 0's prefix). Return number and string"""
    n = int(num)
    return n, str(n)

def small2eng(num):
    '''English representation of a number <= 999'''
    n, num = norm_num(num)
    hundred = ''
    ten = ''
    if len(num) == 3: # Got hundreds
        hundred = get_num(num[0]) + ' hundred'
        num = num[1:]
        n, num = norm_num(num)
    if (n > 20) and (n != (n / 10 * 10)): # Got ones
        tens = get_num(num[0] + '0')
        ones = get_num(num[1])
        ten = tens + ' ' + ones
    else:
        ten = get_num(num)
    if hundred and ten:
        return hundred + ' ' + ten
        #return hundred + ' and ' + ten
    else: # One of the below is empty
        return hundred + ten

#FIXME: Currently num2eng(1012) -> 'one thousand, twelve'
# do we want to add last 'and'?
def num2eng(num):
    '''English representation of a number'''
    num = str(long(num)) # Convert to string, throw if bad number
    if (len(num) / 3 >= len(_PRONOUNCE)): # Sanity check
        raise ValueError('Number too big')
        
    if num == '0': # Zero is a special case
        return 'zero'

    # Create reversed list
    x = list(num)
    x.reverse()
    pron = [] # Result accumolator
    ct = len(_PRONOUNCE) - 1 # Current index
    for a, b, c in triplets(x): # Work on triplets
        p = small2eng(c + b + a)
        if p:
            pron.append(p + ' ' + _PRONOUNCE[ct])
        ct -= 1
    # Create result
    pron.reverse()
    return ', '.join(pron)

if __name__ == '__main__':
    from sys import argv, exit
    from os.path import basename
    if len(argv) < 2:
        print 'usage: %s NUMBER[s]' % basename(argv[0])
        exit(1)
    for n in argv[1:]:
        try:
            print num2eng(n)
        except ValueError, e:
            print 'Error: %s' % e

I modified the code slightly as noted in the comments in the code to match what the VBA code did. Other than that, it's exactly as I found it. I won't explain this piece as it's kind of fun to figure it out for yourself. Hope you like it!

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary