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!
The apostrophe in the norm_num docstring has made a mess of the syntax highlighting.
Oops.. Fixed. Thanks!
Could be. Or I’m thinking of another problem I was working on related to that project.
No problem. I was needing mine back in late 2006. Thanks for the heads-up though!
Posting large code snippets online without clear statement of the license is a useless as creating another fart app for a phone.
If you mean your shared code to be used, declare so by stating licensing terms.