14

What is the most efficient way to convert numeric amount into English words

e.g. 12 to twelve 127 to one hundred twenty-seven

DuduAlul
  • 6,313
  • 7
  • 39
  • 63
  • 4
    Or in English one hundred **and** twenty seven. :) – Philip Smith Jul 21 '10 at 14:24
  • 1
    @Philip, the "and" is wrong if you're talking about proper English. "And" is for fractional parts. – David Gladfelter Jul 21 '10 at 14:49
  • 1
    @David - In British English, if a number includes a smaller number **and** joins them. As in 2010 - two thousand and ten. Another variation is in currency when £1.20 is one pound twenty or one pound and twenty pence. If your bored you could view http://www.english-at-home.com/speaking/saying-dates-and-numbers-in-english/ – Philip Smith Jul 21 '10 at 14:57
  • This link provides a detailed explanation of what looks to be a nice approach: http://www.blackwasp.co.uk/NumberToWords.aspx – robhudson Jul 21 '10 at 13:28

8 Answers8

19

That didn't take long. This is an implementation written in Java.

http://snippets.dzone.com/posts/show/3685

Code

public class IntToEnglish {
    static String[] to_19 = { "zero",  "one",   "two",  "three", "four",   "five",   "six",
        "seven", "eight", "nine", "ten",   "eleven", "twelve", "thirteen",
        "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
    static String[] tens  = { "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
    static String[] denom = { "",
        "thousand",     "million",         "billion",       "trillion",       "quadrillion",
        "quintillion",  "sextillion",      "septillion",    "octillion",      "nonillion",
        "decillion",    "undecillion",     "duodecillion",  "tredecillion",   "quattuordecillion",
        "sexdecillion", "septendecillion", "octodecillion", "novemdecillion", "vigintillion" };

    public static void main(String[] argv) throws Exception {
        int tstValue = Integer.parseInt(argv[0]);
        IntToEnglish itoe = new IntToEnglish();
        System.out.println(itoe.english_number(tstValue));
        /* for (int i = 0; i < 2147483647; i++) {
            System.out.println(itoe.english_number(i));
        } */
    }
    // convert a value < 100 to English.
    private String convert_nn(int val) throws Exception {
        if (val < 20)
            return to_19[val];
        for (int v = 0; v < tens.length; v++) {
            String dcap = tens[v];
            int dval = 20 + 10 * v;
            if (dval + 10 > val) {
                if ((val % 10) != 0)
                    return dcap + "-" + to_19[val % 10];
                return dcap;
            }        
        }
        throw new Exception("Should never get here, less than 100 failure");
    }
    // convert a value < 1000 to english, special cased because it is the level that kicks 
    // off the < 100 special case.  The rest are more general.  This also allows you to
    // get strings in the form of "forty-five hundred" if called directly.
    private String convert_nnn(int val) throws Exception {
        String word = "";
        int rem = val / 100;
        int mod = val % 100;
        if (rem > 0) {
            word = to_19[rem] + " hundred";
            if (mod > 0) {
                word = word + " ";
            }
        }
        if (mod > 0) {
            word = word + convert_nn(mod);
        }
        return word;
    }
    public String english_number(int val) throws Exception {
        if (val < 100) {
            return convert_nn(val);
        }
        if (val < 1000) {
            return convert_nnn(val);
        }
        for (int v = 0; v < denom.length; v++) {
            int didx = v - 1;
            int dval = new Double(Math.pow(1000, v)).intValue();
            if (dval > val) {
                int mod = new Double(Math.pow(1000, didx)).intValue();
                int l = val / mod;
                int r = val - (l * mod);
                String ret = convert_nnn(l) + " " + denom[didx];
                if (r > 0) {
                    ret = ret + ", " + english_number(r);
                }
                return ret;
            }
        }
        throw new Exception("Should never get here, bottomed out in english_number");
    }
}
Sean Allred
  • 3,558
  • 3
  • 32
  • 71
corsiKa
  • 81,495
  • 25
  • 153
  • 204
2

One way to accomplish this would be to use look up tables. It would be somewhat brute force, but easy to setup. For instance, you could have the words for 0-99 in one table, then a table for tens, hundreds, thousands, etc. Some simple math will give you the index of the word you need from each table.

nathan
  • 5,513
  • 4
  • 35
  • 47
  • That would be a huge waste of memory. – Christian Jul 21 '10 at 13:32
  • I agree with your assessment but I did use the term "brute force" in my description. Additionally, there are other answers for this same question that use a look up table approach, including the accepted answer. I'm unsure why your comment only applies to my answer. – nathan Jul 21 '10 at 16:54
2

This is some old python code on my hard drive. There might be bugs but it should show the basic idea:

class Translator:
    def transformInt(self, x):
        translateNumbers(0,[str(x)])

    def threeDigitsNumber(self,number):
        snumber=self.twoDigitsNumber(number/100)
        if number/100!=0:
            snumber+=" hundred "
        snumber+self.twoDigitsNumber(number)
        return snumber+self.twoDigitsNumber(number)
    def twoDigitsNumber(self,number):
        snumber=""
        if number%100==10:
            snumber+="ten"
        elif number%100==11:
            snumber+="eleven"
        elif number%100==12:
            snumber+="twelve"
        elif number%100==13:
            snumber+="thirteen"
        elif number%100==14:
            snumber+="fourteen"
        elif number%100==15:
            snumber+="fifteen"
        elif number%100==16:
            snumber+="sixteen"
        elif number%100==17:
            snumber+="seventeen"
        elif number%100==18:
            snumber+="eighteen"
        elif number%100==19:
            snumber+="nineteen"
        else:
            if (number%100)/10==2:
                snumber+="twenty-"
            elif (number%100)/10==3:
                snumber+="thirty-"
            elif (number%100)/10==4:
                snumber+="forty-"
            elif (number%100)/10==5:
                snumber+="fifty-"
            elif (number%100)/10==6:
                snumber+="sixty-"
            elif (number%100)/10==7:
                snumber+="seventy-"
            elif (number%100)/10==8:
                snumber+="eighty-"
            elif (number%100)/10==9:
                snumber+="ninety-"
            if (number%10)==1:
                snumber+="one"
            elif (number%10)==2:
                snumber+="two"
            elif (number%10)==3:
                snumber+="three"
            elif (number%10)==4:
                snumber+="four"
            elif (number%10)==5:
                snumber+="five"
            elif (number%10)==6:
                snumber+="six"
            elif (number%10)==7:
                snumber+="seven"
            elif (number%10)==8:
                snumber+="eight"
            elif (number%10)==9:
                snumber+="nine"
            elif (number%10)==0:
                if snumber!="":
                    if snumber[len(snumber)-1]=="-":
                        snumber=snumber[0:len(snumber)-1]
        return snumber
    def translateNumbers(self,counter,words):
        if counter+1<len(words):
            self.translateNumbers(counter+1,words)
        else:
            if counter==len(words):
                return True
        k=0
        while k<len(words[counter]): 
            if (not (ord(words[counter][k])>47 and ord(words[counter][k])<58)):
                break
            k+=1
        if (k!=len(words[counter]) or k==0):
            return 1
        number=int(words[counter])
        from copy import copy
        if number==0:
            self.translateNumbers(counter+1,copy(words[0:counter]+["zero"]+words[counter+1:len(words)]))
            self.next.append(copy(words[0:counter]+["zero"]+words[counter+1:len(words)]))
            return 1
        if number<10000:
            self.translateNumbers(counter+1,copy(words[0:counter]
                                                      +self.seperatewords(self.threeDigitsNumber(number))
                                                      +words[counter+1:len(words)]))
            self.next.append(copy(words[0:counter]
                                       +self.seperatewords(self.threeDigitsNumber(number))
                                       +words[counter+1:len(words)]))
        if number>999:
            snumber=""
            if number>1000000000:
                snumber+=self.threeDigitsNumber(number/1000000000)+" billion "
                number=number%1000000000
            if number>1000000:
                snumber+=self.threeDigitsNumber(number/1000000)+" million "
                number=number%1000000
            if number>1000:
                snumber+=self.threeDigitsNumber(number/1000)+" thousand "
                number=number%1000
            snumber+=self.threeDigitsNumber(number)
            self.translateNumbers(counter+1,copy(words[0:counter]+self.seperatewords(snumber)
                                                      +words[counter+1:len(words)]))
            self.next.append(copy(words[0:counter]+self.seperatewords(snumber)
                                       +words[counter+1:len(words)]))
Christian
  • 25,249
  • 40
  • 134
  • 225
2

This solution doesn't attempt to account for trailing spaces, but it is pretty fast.

typedef const char* cstring;
using std::string;
using std::endl;
std::ostream& GetOnes(std::ostream &output, int onesValue)
{
    cstring ones[] = { "zero", "one", "two", "three", "four", "five", "six", 
           "seven", "eight", "nine" };
    output << ones[onesValue];
    return output;
}

std::ostream& GetSubMagnitude(std::ostream &output, int subMagnitude)
{
    cstring tens[] = { "zeroty", "ten", "twenty", "thirty", "fourty", "fifty", 
         "sixty", "seventy", "eighty", "ninety"};
    if (subMagnitude / 100 != 0)
    {       
        GetOnes(output, subMagnitude / 100) << " hundred ";
        GetSubMagnitude(output, subMagnitude - subMagnitude / 100 * 100);
    }
    else
    {
        if (subMagnitude >= 20)
        {
            output << tens[subMagnitude / 10] << " ";
            GetOnes(output, subMagnitude - subMagnitude / 10 * 10);
        }
        else if (subMagnitude >= 10)
        {
            cstring teens[] = { "ten", "eleven", "twelve", "thirteen",
                        "fourteen", "fifteen", "sixteen", "seventeen", 
                         "eighteen", "nineteen" };
            output << teens[subMagnitude - 10] << " ";
        }
        else 
        {
            GetOnes(output, subMagnitude) << " ";
        }
    }
    return output;
}

std::ostream& GetLongNumber(std::ostream &output, double input)
{
    cstring magnitudes[] = {"", "hundred", "thousand", "million", "billion", 
                           "trillion"};
    double magnitudeTests[] = {1, 100.0, 1000.0, 1000000.0, 1000000000.0, 
                           1000000000000.0 };
    int magTestIndex = 0;
    while (floor(input / magnitudeTests[magTestIndex++]) != 0);
    magTestIndex -= 2;
    if (magTestIndex >= 0) 
    {
        double subMagnitude = input / magnitudeTests[magTestIndex];
        GetSubMagnitude(output, (int)subMagnitude);
        if (magTestIndex) {
            output << magnitudes[magTestIndex] << " ";
            double remainder = input - (floor(input / 
                        magnitudeTests[magTestIndex]) * 
                        magnitudeTests[magTestIndex]);
            if (floor(remainder) > 0) 
            {
                GetLongNumber(output, remainder);
            }
        }
    }
    else
    {
        output << "zero";
    }
    return output;
}
David Gladfelter
  • 4,175
  • 2
  • 25
  • 25
  • Obviously, disclaimers include that it doesn't account for numbers greater than 999,999,999,999,999, it doesn't do fractions and it doesn't handle negative numbers. Easy enough to add, but you have to stop at some point. – David Gladfelter Jul 21 '10 at 14:50
2

Start by solving 1-99, using a list of numbers for 1-20, and then 30, 40, ..., 90. Then add hundreds to get 1-999. Then use that routine to give the number of each power of 1,000 for as high as you want to go (I think the highest standard nomenclature is for decillion, which is 10^33).

One slight caveat is that it's a little tricky to get the blanks right in all cases if you're trying to start and end without an excess blank. The easy solution is to put a blank after every word, and then strip off the trailing blank when you're all done. If you try to be more precise while building the string, you're likely to end up with missing blanks or excess blanks.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

.) make a library of all numbers & positions (e.g. 1 has other notation than 10, another than 100 etc) .) make a list of exceptions (e.g. for 12) and be aware, that in your algorythm, the same exception are for 112, 1012 etc.

if you want even more speed, make a cached set of numbers that you need.

cRichter
  • 1,411
  • 7
  • 7
1

Note some rules:

  • Tens numbers (twenty, thirty, etc.) ending in y are followed by hyphens.
  • Teens are special (except 15-19, but they're still special).
  • Everything else is just some combination of digit place like "three thousand".

You can get the place of a number by using floor division of integers: 532 / 100 -> 5

nmichaels
  • 49,466
  • 12
  • 107
  • 135
0

This is part of Common Lisp!

Here's how GNU CLISP does it, and here's how CMUCL does it (easier to read, IMHO).

Doing a code search for "format million billion" will turn up lots of them.

Ken
  • 64
  • 1