# How to Convert Decimal Numbers to Words with Python

It may have been a better idea to have called this this article “How to Convert Floats to Words”, but since I’m talking about currency, I thought using Decimal was more accurate. Anyway, a couple years ago, I wrote about how to convert numbers to Python. The main reason I’m revisiting this topic is because I ended up needing to do it again and I found my own example rather lacking. It doesn’t show how to actually use it to convert something like “10.12” into “ten dollars and twelve cents”. So I’m going to show you how to do it in this article and then we’ll also look at some of the alternatives that my readers gave me.

### Back to the Drawing Board

To start out, we’ll take the original code and add some tests on the end to make sure it works the way we want. Then I’ll show you a slightly different way to go about it. Finally, we will look at two other projects that attempt to do this sort of thing as well.

```'''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 <tebeka@cs.bgu.ac.il>'
__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',
'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) + ' hundred'
num = num[1:]
n, num = norm_num(num)
if (n > 20) and (n != (n / 10 * 10)): # Got ones
tens = get_num(num + '0')
ones = get_num(num)
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

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__':

numbers = [1.37, 0.07, 123456.00, 987654.33]
for number in numbers:
dollars, cents = [int(num) for num in str(number).split(".")]

dollars = num2eng(dollars)
if dollars.strip() == "one":
dollars = dollars + "dollar and "
else:
dollars = dollars + "dollars and "

cents = num2eng(cents) + "cents"
print dollars + cents```

We’re only going to focus on the last section that tests the program. Here we have a list of various values that we run through the program and make sure it spits out what we want. Note that we have an amount that’s less than a dollar. This is an edge case that I’ve seen used because my employer wants to test our code with real amounts, but doesn’t want huge amounts of money being transferred. Here’s a slightly different way to output the data:

```temp_amount = 10.34
if '.' in temp_amount:
amount = temp_amount.split('.')
dollars = amount
cents = amount
else:
dollars = temp_amount
cents = '00'

amt = num2eng.num2eng(dollars)
total = amt + 'and %s/100 Dollars' % cents
print total```

In this case, we don’t write out the cents portion as words but just put the number over one hundred. Yeah, I know it’s subtle, but this article is also a brain dump for me so the next time I have to do this I’ll have all the information at my fingertips.

### Trying out PyNum2Word

After I posted my original article, someone came along and told me about the PyNum2Word project and how I should have used that. The PyNum2Word project didn’t exist back then, but I decided to give it a try this time around. Sadly this project has no documentation that I could find. Not even a README file! On the other hand, it claims it can do currency for the USA, Germany, Great Britain, the EU and France. I thought Germany, Britain and France were in the EU though, so I’m not sure what the point is in doing francs and such when they all use Euros now.

Anyway, in our case we’ll use the following file, num2word_EN.py, from their package for our tests. There is actually a test at the bottom of the file that is similar to the one I built. I actually based my test on theirs. Let’s try editing the file and add a number less than one to their second list, such as 0.45, to see if that works. Here is the result of the second list’s output (I’m skipping the first list’s output for brevity):

``` 0.45 is zero point four five cents 0.45 is zero point four five 1 is one cent 1 is one 120 is one dollar and twenty cents 120 is one hundred and twenty 1000 is ten dollars 1000 is one thousand 1120 is eleven dollars and twenty cents 1120 is eleven hundred and twenty 1800 is eighteen dollars 1800 is eighteen hundred 1976 is nineteen dollars and seventy-six cents 1976 is nineteen hundred and seventy-six 2000 is twenty dollars 2000 is two thousand 2010 is twenty dollars and ten cents 2010 is two thousand and ten 2099 is twenty dollars and ninety-nine cents 2099 is two thousand and ninety-nine 2171 is twenty-one dollars and seventy-one cents 2171 is twenty-one hundred and seventy-one ```

It works, but not in the way I expected. In the USA, when we talk about currency, we would call 0.45 “forty-five cents” not “zero point four five cents”. When I was researching this, I did learn that people in some other countries do use the latter terminology. What I find interesting is that this module takes anything above 100 and divides it into dollars and cents. For example, note that 120 is translated into “one dollar and twenty cents” instead of “one hundred and twenty dollars”. Also note that it says “twenty cents”, NOT “point two zero cents”. I don’t know how to explain that contradiction. It DOES work if you pass it just an integer that’s less than 100. So if you had the user put in a float, you would want to break it up much like I did earlier:

```if '.' in temp_amount:
amount = temp_amount.split('.')
dollars = amount
cents = amount
else:
dollars = temp_amount
cents = '00'```

Then pass each part through the script to get the pieces and then put them together.

### Using numbers.py

Another of my readers by the name of Eric Wald contacted me about his numbers.py script. Let’s see how that holds up!

Looking at the code, you’ll quickly find that it cannot handle floats, so we’ll have to break up our floats and pass it the dollars and cents separately. I tried this with several different numbers and it seems to convert them correctly. The script even puts commas in at the thousand’s mark. It doesn’t add the “and” word anywhere, but I don’t care about that right now.

### Conclusion

All three of these methods require a wrapper of some sort to add the “dollars” and “cents” (or number/100) words to them and to break up the float into two pieces. I think Eric’s code is very straightforward and the best documented. The PyNum2Word project’s code is also very succinct and works quite well, but there’s no documentation. The solution I found a long time ago also works, but I find that code very ugly and not very easy to read. I don’t really have a recommendation, but I think I like Eric’s the best. If you need the flexibility of doing multiple currencies, then the PyNum2Word project is worth a look.