Converting Cardinal Numbers to Ordinal using C#


Introduction

A problem which often arises in practice is how to convert a cardinal number to its ordinal equivalent.

As the .NET Framework doesn't contain any methods which perform this particular conversion, this article presents some extension methods which can be used for the purpose.

What are cardinal and ordinal numbers anyway?

A cardinal number is simply one of the 'natural numbers', including zero, namely:

0, 1, 2, 3, 4, 5,...

An ordinal number gives the position of something in a list:

0th, 1st, 2nd, 3rd, 4th, 5th,...

The suffixes are derived from how ordinal numbers are written:

first, second, third, fourth, fifth, ...

Notice that negative numbers are not included in the sets of cardinal and ordinal numbers.

The rule for determining the ordinal equivalent of a cardinal number is that, if the number ends with:

   1 then add "st"

   2 then add "nd"

   3 then add "rd"

   otherwise add "th" 

The only exception to this rule is that, if the number divided by 100 equals 11, 12 or 13, then "th" should be added rather than the above suffixes.

Are there any problems in programming the conversion methods?

Not really.

If the input is a type of integer, then the coding is very easy. We just need to code extension methods for the 'long' and 'ulong' types. Methods for the other integral types can then simply call one or other of these two methods.

If a negative number is input, then the method can simply return the number (converted to a string) rather than throw an exception.

What if the number is expressed in words?

This is still quite easy.

All we need is a dictionary to map numeric words to their ordinal equivalents.

This dictionary can then be used to do a rough check that the input is a valid numeric string and, if it is, the final (or only) word can be extracted, converted to its ordinal equivalent and re-attached to the rest of the string.

If the rough check fails to confirm that it's a valid numeric string (for example it includes the word 'minus') then the input can simply be returned unaltered.

A slight problem is to replace the final word using the original character casing. This is easy enough if the final word is wholly upper or lower case. Where it's mixed case, I decided to replace it using 'title' case (i.e. only the first letter is capitalized).

Source code

The source code for three of the extension methods is reproduced below, namely for long, int and string inputs, together with some test code:

using System;
using System.Collections.Generic;
using Utilities;

namespace Utilities
{
    static class MathExtensions
    {
        public static string ToOrdinal(this long number)
        {
            if (number < 0) return number.ToString();
            long rem = number % 100;
            if (rem >= 11 && rem <= 13) return number + "th";

            switch (number % 10)
            {
                case 1:
                    return number + "st";
                case 2:
                    return number + "nd";
                case 3:
                    return number + "rd";
                default:
                    return number + "th";
            }
        }

        public static string ToOrdinal(this int number)
        {
            return ((long)number).ToOrdinal();
        }

        public static string ToOrdinal(this string number)
        {
            if (String.IsNullOrEmpty(number)) return number;

            var dict = new Dictionary<string, string>();
            dict.Add("zero", "zeroth");
            dict.Add("nought", "noughth");
            dict.Add("one", "first");
            dict.Add("two", "second");
            dict.Add("three", "third");
            dict.Add("four", "fourth");
            dict.Add("five", "fifth");
            dict.Add("six", "sixth");
            dict.Add("seven", "seventh");
            dict.Add("eight", "eighth");
            dict.Add("nine", "ninth");
            dict.Add("ten", "tenth");
            dict.Add("eleven", "eleventh");
            dict.Add("twelve", "twelfth");
            dict.Add("thirteen", "thirteenth");
            dict.Add("fourteen", "fourteenth");
            dict.Add("fifteen", "fifteenth");
            dict.Add("sixteen", "sixteenth");
            dict.Add("seventeen", "seventeenth");
            dict.Add("eighteen", "eighteenth");
            dict.Add("nineteen", "nineteenth");
            dict.Add("twenty", "twentieth");
            dict.Add("thirty", "thirtieth");
            dict.Add("forty", "fortieth");
            dict.Add("fifty", "fiftieth");
            dict.Add("sixty", "sixtieth");
            dict.Add("seventy", "seventieth");
            dict.Add("eighty", "eightieth");
            dict.Add("ninety", "ninetieth");
            dict.Add("hundred", "hundredth");
            dict.Add("thousand", "thousandth");
            dict.Add("million", "millionth");

            dict.Add("billion", "billionth");
            dict.Add("trillion", "trillionth");
            dict.Add("quadrillion", "quadrillionth");
            dict.Add("quintillion", "quintillionth");

            // rough check whether it's a valid number
            string temp = number.ToLower().Trim().Replace(" and ", " ");
            string[] words = temp.Split(new char[] { ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string word in words)
            {
                if (!dict.ContainsKey(word)) return number;
            }

            // extract last word
            number = number.TrimEnd().TrimEnd('-');
            int index = number.LastIndexOfAny(new char[] { ' ', '-' });
            string last = number.Substring(index + 1);

            // make replacement and maintain original capitalization
            if (last == last.ToLower())
            {
                last = dict[last];
            }
            else if (last == last.ToUpper())
            {
                last = dict[last.ToLower()].ToUpper();
            }
            else
            {
                last = last.ToLower();
                last = Char.ToUpper(dict[last][0]) + dict[last].Substring(1);
            }

            return number.Substring(0, index + 1) + last;
        }

    }
}

class Program
{
    static void Main()
    {
        int[] ia = { 0, 1, 2, 3, 4, 11, 12, 13, 15, 23, 99, 1001, 72003, -2 };

        foreach (int i in ia)
        {
            Console.WriteLine("{0} -> {1}", i, i.ToOrdinal());
        }

        Console.WriteLine();
        string[] sa = { "Zero", "one", "TWO", "THree", "Twenty-One", "One hundred and eight", "Thousand-One", "Minus Seven", "Nine words" };
        foreach (string s in sa)
        {
            Console.WriteLine("{0} -> {1}", s, s.ToOrdinal());
        }
        Console.ReadKey();
    }
}


Results

The following is a screen-shot showing the results of running this program:

CrdnlNum.jpg

Notice that -2, 'Minus Seven' and 'Nine words' are returned unaltered as they are invalid inputs.

Conclusion

Although these methods are not difficult to code, anything which means we have to 're-invent less wheels' is generally welcome.

The code for the integral types other than long and int is included in the accompanying download.

Notice that the extension method which takes a string input can be used as an adjunct to my NumberToText method (http://www.c-sharpcorner.com/uploadfile/b942f9/converting-numbers-to-words-in-C-Sharp/ )  where an ordinal rather than a cardinal representation is required.

Up Next
    Ebook Download
    View all
    Learn
    View all