(Print this page)

.Net code to convert numbers to words
Published date: Wednesday, February 15, 2017
On: Moer and Éric Moreau's web site

Here is some .Net code to solve a real problem I had lately!

I had a case where I needed to convert a number into words for writing on a cheque (or a check if you prefer).

There are some algorithms on the Internet. Many have issues when come to the thousands. All of them had failed for my main requirement: being able to produce the words in French (in addition to English).

By chance, on the topic of writing a number in words, both English and French are somewhat similar in the building of the words except that the French language as a lot of little subtilties like adding s here and there and prefixing with un for numbers like 100 and 1000 (which are cent and mille).

Downloadable code

This month solution contains both VB and C# projects. The solution was created using Visual Studio 2017 RC but the code should work as well in older version of the.Net Framework.

A very simple user interface

As you can see in figure 1, only a data grid view control is shown on the screen. The reason is that the take away of this article is the class named Converter which does all the job.

Figure 1: The demo application in action

The Converter class

This is what you will want to copy to your own project!

This class takes care of all the conversion from a number to its words equivalent value in either English or French.

Really, the only method that needs to be public is the one named ConvertNumberToWords but because the demo solution also contains unit tests, all methods are made public.

You should never call any methods other than ConvertNumberToWords directly in your code. Just call this method passing your integer value you want to convert and the language and a string will be returned to you.

Option Strict On

Public Enum Language
    English = 0
    French = 1
End Enum

Public Class Converter

    Public Shared Function ConvertNumberToWords(pValue As Integer, pLanguage As Language) As String
        Dim strReturn As String
        If pValue < 0 Then
            Throw New NotSupportedException("negative numbers not supported")
        ElseIf pValue = 0 Then
            strReturn = If(pLanguage = Language.English, "zero", "zéro")
        ElseIf pValue < 10 Then
            strReturn = ConvertDigitToWords(pValue, pLanguage)
        ElseIf pValue < 20 Then
            strReturn = ConvertTeensToWords(pValue, pLanguage)
        ElseIf pValue < 100 Then
            strReturn = ConvertHighTensToWords(pValue, pLanguage)
        ElseIf pValue < 1000 Then
            strReturn = ConvertBigNumberToWords(pValue, 100, "hundred", pLanguage)
        ElseIf pValue < 1000000 Then
            strReturn = ConvertBigNumberToWords(pValue, 1000, "thousand", pLanguage)
        ElseIf pValue < 1000000000 Then
            strReturn = ConvertBigNumberToWords(pValue, 1000000, "million", pLanguage)
        Else
            Throw New NotSupportedException("Number is too large!!!")
        End If

        If pLanguage = Language.French Then
            If strReturn.EndsWith("quatre-vingt") Then
                'another French exception
                strReturn += "s"
            End If
        End If
        Return strReturn
    End Function

    Public Shared Function ConvertDigitToWords(pValue As Integer, pLanguage As Language) As String
        Select Case pValue
            Case 0
                Return ""
            Case 1
                Return If(pLanguage = Language.English, "one", "un")
            Case 2
                Return If(pLanguage = Language.English, "two", "deux")
            Case 3
                Return If(pLanguage = Language.English, "three", "trois")
            Case 4
                Return If(pLanguage = Language.English, "four", "quatre")
            Case 5
                Return If(pLanguage = Language.English, "five", "cinq")
            Case 6
                Return "six"
            Case 7
                Return If(pLanguage = Language.English, "seven", "sept")
            Case 8
                Return If(pLanguage = Language.English, "eight", "huit")
            Case 9
                Return If(pLanguage = Language.English, "nine", "neuf")
            Case Else
                Throw New IndexOutOfRangeException("{pValue} not a digit")
        End Select
    End Function

    'assumes a number between 10 & 19
    Public Shared Function ConvertTeensToWords(pValue As Integer, pLanguage As Language) As String
        Select Case pValue
            Case 10
                Return If(pLanguage = Language.English, "ten", "dix")
            Case 11
                Return If(pLanguage = Language.English, "eleven", "onze")
            Case 12
                Return If(pLanguage = Language.English, "twelve", "douze")
            Case 13
                Return If(pLanguage = Language.English, "thirteen", "treize")
            Case 14
                Return If(pLanguage = Language.English, "fourteen", "quatorze")
            Case 15
                Return If(pLanguage = Language.English, "fifteen", "quinze")
            Case 16
                Return If(pLanguage = Language.English, "sixteen", "seize")
            Case 17
                Return If(pLanguage = Language.English, "seventeen", "dix-sept")
            Case 18
                Return If(pLanguage = Language.English, "eighteen", "dix-huit")
            Case 19
                Return If(pLanguage = Language.English, "nineteen", "dix-neuf")
            Case Else
                Throw New IndexOutOfRangeException("{pValue} not a teen")
        End Select
    End Function

    'assumes a number between 20 and 99
    Public Shared Function ConvertHighTensToWords(pValue As Integer, pLanguage As Language) As String
        Dim tensDigit As Integer = CInt(Math.Floor(CDbl(pValue) / 10.0))

        Dim tensStr As String
        Select Case tensDigit
            Case 2
                tensStr = If(pLanguage = Language.English, "twenty", "vingt")
                Exit Select
            Case 3
                tensStr = If(pLanguage = Language.English, "thirty", "trente")
                Exit Select
            Case 4
                tensStr = If(pLanguage = Language.English, "forty", "quarante")
                Exit Select
            Case 5
                tensStr = If(pLanguage = Language.English, "fifty", "cinquante")
                Exit Select
            Case 6
                tensStr = If(pLanguage = Language.English, "sixty", "soixante")
                Exit Select
            Case 7
                tensStr = If(pLanguage = Language.English, "seventy", "soixante-dix")
                Exit Select
            Case 8
                tensStr = If(pLanguage = Language.English, "eighty", "quatre-vingt")
                Exit Select
            Case 9
                tensStr = If(pLanguage = Language.English, "ninety", "quatre-vingt-dix")
                Exit Select
            Case Else
                Throw New IndexOutOfRangeException("{pValue} not in range 20-99")
        End Select

        If pValue Mod 10 = 0 Then Return tensStr

        'French sometime has a prefix in front of 1
        Dim strPrefix As String = String.Empty
        If pLanguage = Language.French AndAlso (tensDigit < 8) AndAlso (pValue - tensDigit * 10 = 1) Then
            strPrefix = "-et"
        End If

        Dim onesStr As String
        If pLanguage = Language.French AndAlso (tensDigit = 7 OrElse tensDigit = 9) Then
            tensStr = ConvertHighTensToWords(10 * (tensDigit - 1), pLanguage)
            onesStr = ConvertTeensToWords(10 + pValue - tensDigit * 10, pLanguage)
        Else
            onesStr = ConvertDigitToWords(pValue - tensDigit * 10, pLanguage)
        End If

        Return Convert.ToString((tensStr & strPrefix) + "-") & onesStr
    End Function

    ' Use this to convert any integer bigger than 99
    Public Shared Function ConvertBigNumberToWords(pValue As Integer, baseNum As Integer, baseNumStr As String, pLanguage As Language) As String
        ' special case: use commas to separate portions of the number, unless we are in the hundreds
        Dim separator As String
        If pLanguage = Language.French Then
            separator = " "
        Else
            separator = If((baseNumStr <> "hundred"), ", ", " ")
        End If

        ' Strategy: translate the first portion of the number, then recursively translate the remaining sections.
        ' Step 1: strip off first portion, and convert it to string:
        Dim bigPart As Integer = CInt(Math.Floor(CDbl(pValue) / baseNum))
        Dim bigPartStr As String
        If pLanguage = Language.French Then
            Dim baseNumStrFrench As String
            Select Case baseNumStr
                Case "hundred"
                    baseNumStrFrench = "cent"
                    Exit Select
                Case "thousand"
                    baseNumStrFrench = "mille"
                    Exit Select
                Case "million"
                    baseNumStrFrench = "million"
                    Exit Select
                Case "billion"
                    baseNumStrFrench = "milliard"
                    Exit Select
                Case Else
                    baseNumStrFrench = "????"
                    Exit Select
            End Select
            If bigPart = 1 AndAlso pValue < 1000000 Then
                bigPartStr = baseNumStrFrench
            Else
                bigPartStr = Convert.ToString(ConvertNumberToWords(bigPart, pLanguage) & Convert.ToString(" ")) & baseNumStrFrench
            End If
        Else
            bigPartStr = Convert.ToString(ConvertNumberToWords(bigPart, pLanguage) & Convert.ToString(" ")) & baseNumStr
        End If

        ' Step 2: check to see whether we're done:
        If pValue Mod baseNum = 0 Then
            If pLanguage = Language.French Then
                If bigPart > 1 Then
                    'in French, a s is required to cent/mille/million/milliard if there is a value in front but nothing after
                    Return bigPartStr & Convert.ToString("s")
                Else
                    Return bigPartStr
                End If
            Else
                Return bigPartStr
            End If
        End If

        ' Step 3: concatenate 1st part of string with recursively generated remainder:
        Dim restOfNumber As Integer = pValue - bigPart * baseNum
        Return Convert.ToString(bigPartStr & separator) & ConvertNumberToWords(restOfNumber, pLanguage)
    End Function

End Class
using System;

namespace DemoNumberToWords
{

    public enum Language
    {
        English = 0,
        French = 1
    }
    public class Converter
    {

        public static string ConvertNumberToWords(int pValue, Language pLanguage)
        {
            string strReturn;
            if (pValue < 0)
                throw new NotSupportedException("negative numbers not supported");
            else if (pValue == 0)
                strReturn=pLanguage == Language.English ? "zero" : "zéro";
            else if (pValue < 10)
                strReturn= ConvertDigitToWords(pValue, pLanguage);
            else if (pValue < 20)
                strReturn = ConvertTeensToWords(pValue, pLanguage);
            else if (pValue < 100)
                strReturn = ConvertHighTensToWords(pValue, pLanguage);
            else if (pValue < 1000)
                strReturn = ConvertBigNumberToWords(pValue, 100, "hundred", pLanguage);
            else if (pValue < 1000000)
                strReturn = ConvertBigNumberToWords(pValue, 1000, "thousand", pLanguage);
            else if (pValue < 1000000000)
                strReturn = ConvertBigNumberToWords(pValue, 1000000, "million", pLanguage);
            else
                throw new NotSupportedException("Number is too large!!!");

            if (pLanguage == Language.French)
            {
                if (strReturn.EndsWith("quatre-vingt"))
                {
                    //another French exception
                    strReturn += "s";
                }
            }
            return strReturn;
        }

        public static string ConvertDigitToWords(int pValue, Language pLanguage)
        {
            switch (pValue)
            {
                case 0: return "";
                case 1: return pLanguage == Language.English ? "one" : "un";
                case 2: return pLanguage == Language.English ? "two" : "deux";
                case 3: return pLanguage == Language.English ? "three" : "trois";
                case 4: return pLanguage == Language.English ? "four" : "quatre";
                case 5: return pLanguage == Language.English ? "five" : "cinq";
                case 6: return "six";
                case 7: return pLanguage == Language.English ? "seven" : "sept";
                case 8: return pLanguage == Language.English ? "eight" : "huit";
                case 9: return pLanguage == Language.English ? "nine" : "neuf";
                default:
                    throw new IndexOutOfRangeException($"{pValue} not a digit");
            }
        }

        //assumes a number between 10 & 19
        public static string ConvertTeensToWords(int pValue, Language pLanguage)
        {
            switch (pValue)
            {
                case 10: return pLanguage == Language.English ? "ten" : "dix";
                case 11: return pLanguage == Language.English ? "eleven" : "onze";
                case 12: return pLanguage == Language.English ? "twelve" : "douze";
                case 13: return pLanguage == Language.English ? "thirteen" : "treize";
                case 14: return pLanguage == Language.English ? "fourteen" : "quatorze";
                case 15: return pLanguage == Language.English ? "fifteen" : "quinze";
                case 16: return pLanguage == Language.English ? "sixteen" : "seize";
                case 17: return pLanguage == Language.English ? "seventeen" : "dix-sept";
                case 18: return pLanguage == Language.English ? "eighteen" : "dix-huit";
                case 19: return pLanguage == Language.English ? "nineteen" : "dix-neuf";
                default:
                    throw new IndexOutOfRangeException($"{pValue} not a teen");
            }
        }

        //assumes a number between 20 and 99
        public static string ConvertHighTensToWords(int pValue, Language pLanguage)
        {
            int tensDigit = (int)(Math.Floor((double)pValue / 10.0));

            string tensStr;
            switch (tensDigit)
            {
                case 2: tensStr = pLanguage == Language.English ? "twenty" : "vingt"; break;
                case 3: tensStr = pLanguage == Language.English ? "thirty" : "trente"; break;
                case 4: tensStr = pLanguage == Language.English ? "forty" : "quarante"; break;
                case 5: tensStr = pLanguage == Language.English ? "fifty" : "cinquante"; break;
                case 6: tensStr = pLanguage == Language.English ? "sixty" : "soixante"; break;
                case 7: tensStr = pLanguage == Language.English ? "seventy" : "soixante-dix"; break;
                case 8: tensStr = pLanguage == Language.English ? "eighty" : "quatre-vingt"; break;
                case 9: tensStr = pLanguage == Language.English ? "ninety" : "quatre-vingt-dix"; break;
                default:
                    throw new IndexOutOfRangeException($"{pValue} not in range 20-99");
            }

            if (pValue % 10 == 0) return tensStr;

            //French sometime has a prefix in front of 1
            string strPrefix = string.Empty;
            if (pLanguage == Language.French && (tensDigit < 8) && (pValue - tensDigit * 10 == 1))
                strPrefix = "-et";

            string onesStr;
            if (pLanguage == Language.French && (tensDigit == 7 || tensDigit == 9))
            {
                tensStr = ConvertHighTensToWords(10 * (tensDigit - 1), pLanguage);
                onesStr = ConvertTeensToWords(10 + pValue - tensDigit * 10, pLanguage);
            }
            else
                onesStr = ConvertDigitToWords(pValue - tensDigit * 10, pLanguage);

            return tensStr + strPrefix + "-" + onesStr;
        }

        // Use this to convert any integer bigger than 99
        public static string ConvertBigNumberToWords(int pValue, int baseNum, string baseNumStr, Language pLanguage)
        {
            // special case: use commas to separate portions of the number, unless we are in the hundreds
            string separator;
            if (pLanguage == Language.French)
                separator = " ";
            else
                separator= (baseNumStr != "hundred") ? ", " : " ";

            // Strategy: translate the first portion of the number, then recursively translate the remaining sections.
            // Step 1: strip off first portion, and convert it to string:
            int bigPart = (int)(Math.Floor((double)pValue / baseNum));
            string bigPartStr;
            if (pLanguage == Language.French)
            {
                string baseNumStrFrench;
                switch (baseNumStr)
                {
                    case "hundred":
                        baseNumStrFrench = "cent";
                        break;
                    case "thousand":
                        baseNumStrFrench = "mille";
                        break;
                    case "million":
                        baseNumStrFrench = "million";
                        break;
                    case "billion":
                        baseNumStrFrench = "milliard";
                        break;
                    default:
                        baseNumStrFrench = "????";
                        break;
                }
                if (bigPart == 1 && pValue < 1000000)
                    bigPartStr = baseNumStrFrench;
                else
                    bigPartStr = ConvertNumberToWords(bigPart, pLanguage) + " " + baseNumStrFrench;
            }
            else
                bigPartStr = ConvertNumberToWords(bigPart, pLanguage) + " " + baseNumStr;

            // Step 2: check to see whether we're done:
            if (pValue % baseNum == 0)
            {
                if (pLanguage == Language.French)
                {
                    if (bigPart > 1)
                    {
                        //in French, a s is required to cent/mille/million/milliard if there is a value in front but nothing after
                        return bigPartStr + "s";
                    }
                    else
                        return bigPartStr;
                }
                else
                    return bigPartStr;
            }

            // Step 3: concatenate 1st part of string with recursively generated remainder:
            int restOfNumber = pValue - bigPart * baseNum;
            return bigPartStr + separator + ConvertNumberToWords(restOfNumber, pLanguage);
        }
    }
}

Building the test UI

To offer a visual test to my class, I have created a very simple which host a single control (a DataGridView) to show some converted values.

In the Shown event of the class, I create a list of Translation to hold the results that will be later shown in the data grid.

The Translation reads like this:

Public Class Translation
    Public Property Value As Integer
    Public Property English As String
    Public Property French As String
End Class
public class Translation
{
    public int Value { get; set; }
    public string English { get; set; }
    public string French { get; set; }
}

The code then loops a number of times calling the ConvertNumberToWords method in both English and French. Once done, the result list is displayed. The whole code reads like this:

Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    Dim results As New List(Of Translation)
    Dim values As Integer() = Enumerable.Range(0, 10001).ToArray()
    For Each v As Integer In values
        Dim t As New Translation()
        t.Value = v
        t.English = Converter.ConvertNumberToWords(v, Language.English)
        t.French = Converter.ConvertNumberToWords(v, Language.French)
        results.Add(t)
    Next

    dataGridView1.DataSource = results
End Sub
private void Form1_Shown(object sender, EventArgs e)
{
    List<Translation> results = new List<Translation>();
    int[] values = Enumerable.Range(0, 10001).ToArray();
    foreach (int v in values)
    {
        Translation t = new Translation();
        t.Value = v;
        t.English = Converter.ConvertNumberToWords(v, Language.English);
        t.French= Converter.ConvertNumberToWords(v, Language.French);
        results.Add(t);
    }

    dataGridView1.DataSource = results;
}

Unit testing

If you download the demo solution, you will also find unit test classes (one for each project). It was a lot easier to test using unit testing and ensuring that nothing else got broken after I made some modifications to my translation algorithm.

Conclusion

A useful class for you to bring to your project whenever you need to convert a number into words. Especially if you want the French value!


(Print this page)