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:
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.