14
$\begingroup$

Is there a built-in way to convert an integer into its ordinal string representation (as per this page). That is, something akin to

{1 -> "1st", 2 -> "2nd", (*etc...*)}

or

{1 -> "First", 2 -> "Second", (*etc...*)}
$\endgroup$
2
  • $\begingroup$ I have now migrated my answer to this post (see below). In short: As of Version 10 there is IntegerName but it needs to be tweaked to give what you/we want. $\endgroup$
    – gwr
    Jan 31, 2019 at 11:25
  • $\begingroup$ Consider ResourceFunction["OrdinalNumberString"] from the Wolfram Resource Library. $\endgroup$
    – creidhne
    Jan 14, 2021 at 2:45

6 Answers 6

17
$\begingroup$

The coolest way is to check the answer to this question by David Carraher. I am shamelessly stealing his code here to write a function that gives you rules for up to maxNumber:

ordinalRule[maxNumber_Integer] /; maxNumber > 0 := 
  Block[{p},
    Thread[
      Function[{x}, 
        x -> StringSplit[SpokenString[p[[#]]]][[2]] &[x] // Quiet] /@ Range[maxNumber]
  ]];

For example:

ordinalRule[100]

(*out*){1 -> "1st", 2 -> "2nd", 3 -> "3rd", 4 -> "4th", 5 -> "5th", 
 6 -> "6th", 7 -> "7th", 8 -> "8th", 9 -> "9th", 10 -> "10th", 
 11 -> "11th", 12 -> "12th", 13 -> "13th", 14 -> "14th", 15 -> "15th",
  16 -> "16th", 17 -> "17th", 18 -> "18th", 19 -> "19th", 
 20 -> "20th", 21 -> "21st", 22 -> "22nd", 23 -> "23rd", 24 -> "24th",
  25 -> "25th", 26 -> "26th", 27 -> "27th", 28 -> "28th", 
 29 -> "29th", 30 -> "30th", 31 -> "31st", 32 -> "32nd", 33 -> "33rd",
  34 -> "34th", 35 -> "35th", 36 -> "36th", 37 -> "37th", 
 38 -> "38th", 39 -> "39th", 40 -> "40th", 41 -> "41st", 42 -> "42nd",
  43 -> "43rd", 44 -> "44th", 45 -> "45th", 46 -> "46th", 
 47 -> "47th", 48 -> "48th", 49 -> "49th", 50 -> "50th", 51 -> "51st",
  52 -> "52nd", 53 -> "53rd", 54 -> "54th", 55 -> "55th", 
 56 -> "56th", 57 -> "57th", 58 -> "58th", 59 -> "59th", 60 -> "60th",
  61 -> "61st", 62 -> "62nd", 63 -> "63rd", 64 -> "64th", 
 65 -> "65th", 66 -> "66th", 67 -> "67th", 68 -> "68th", 69 -> "69th",
  70 -> "70th", 71 -> "71st", 72 -> "72nd", 73 -> "73rd", 
 74 -> "74th", 75 -> "75th", 76 -> "76th", 77 -> "77th", 78 -> "78th",
  79 -> "79th", 80 -> "80th", 81 -> "81st", 82 -> "82nd", 
 83 -> "83rd", 84 -> "84th", 85 -> "85th", 86 -> "86th", 87 -> "87th",
  88 -> "88th", 89 -> "89th", 90 -> "90th", 91 -> "91st", 
 92 -> "92nd", 93 -> "93rd", 94 -> "94th", 95 -> "95th", 96 -> "96th",
  97 -> "97th", 98 -> "98th", 99 -> "99th", 100 -> "100th"}

I don't know whether this counts as built-in though.

----EDIT----

From @Wreach's comment: there is an undocumented built-in (that SpokenString calls when dealing with numbers to ordinals) that does exactly that:

Speak["stackexchange"];
SpokenStringDump`SpeakOrdinal[121]
(*out*) 121st

In v10 I need to call Speak before the relevant function auto-loads.

$\endgroup$
2
  • 2
    $\begingroup$ Might not be completely builtin, but still very neat. Nice find! $\endgroup$
    – Daniel
    Nov 4, 2014 at 15:40
  • 10
    $\begingroup$ +1 It uses an undocumented internal function like this: SpokenStringDump`SpeakOrdinal[1] returning "1st". You might have to reference the symbol Speak first to make sure that the package is auto-loaded. $\endgroup$
    – WReach
    Nov 4, 2014 at 18:36
10
$\begingroup$

I noticed only afterwards that you were asking for a built-in method. I'm not certain if you would actually want to rely on an undocumented feature instead of writing your own, since it's not hard.

This requires v10 for StringTemplate. It could be replaced with something else, of course...

Range[120] /.
 { tens_Integer /; Floor[Mod[tens, 100], 10] == 10 :> StringTemplate["`1`th"][tens],
   firsts_Integer /; Mod[firsts, 10] == 1 :> StringTemplate["`1`st"][firsts],
   seconds_Integer /; Mod[seconds, 10] == 2 :> StringTemplate["`1`nd"][seconds],
   thirds_Integer /; Mod[thirds, 10] == 3 :> StringTemplate["`1`rd"][thirds],
   rest_Integer :> StringTemplate["`1`th"][rest] }

(* { "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th",
     "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th",
     "18th", "19th", "20th", "21st", "22nd", "23rd", "24th", "25th",
     "26th", "27th", "28th", "29th", "30th", "31st", "32nd", "33rd",
     "34th", "35th", "36th", "37th", "38th", "39th", "40th", "41st",
     "42nd", "43rd", "44th", "45th", "46th", "47th", "48th", "49th",
     "50th", "51st", "52nd", "53rd", "54th", "55th", "56th", "57th",
     "58th", "59th", "60th", "61st", "62nd", "63rd", "64th", "65th",
     "66th", "67th", "68th", "69th", "70th", "71st", "72nd", "73rd",
     "74th", "75th", "76th", "77th", "78th", "79th", "80th", "81st",
     "82nd", "83rd", "84th", "85th", "86th", "87th", "88th", "89th",
     "90th", "91st", "92nd", "93rd", "94th", "95th", "96th", "97th",
     "98th", "99th", "100th", "101st", "102nd", "103rd", "104th", 
     "105th", "106th", "107th", "108th", "109th", "110th", "111th",
     "112th", "113th", "114th", "115th", "116th", "117th", "118th",
     "119th", "120th" } *)
$\endgroup$
6
$\begingroup$

A brute force but very simple approach (no undocumented functions needed) that I think will work with any version of Mathematica.

ordinalRule[n_Integer /; Mod[n, 100] == 11] := generalRule[n]
ordinalRule[n_Integer /; Mod[n, 100] == 12] := generalRule[n]
ordinalRule[n_Integer /; Mod[n, 100] == 13] := generalRule[n]
ordinalRule[n_Integer /; Mod[n, 10] == 1] = specialRule1[n];
ordinalRule[n_Integer /; Mod[n, 10] == 2] = specialRule2[n];
ordinalRule[n_Integer /; Mod[n, 10] == 3] = specialRule3[n];
ordinalRule[n_Integer] = generalRule[n];
specialRule1[n_] := n -> (ToString[n] <> "st")
specialRule2[n_] := n -> (ToString[n] <> "nd")
specialRule3[n_] := n -> (ToString[n] <> "rd")
generalRule[n_] := n -> (ToString[n] <> "th")

ordinalRule /@ Range @ 130
{1 -> "1st", 2 -> "2nd", 3 -> "3rd", 4 -> "4th", 5 -> "5th", 
 6 -> "6th", 7 -> "7th", 8 -> "8th", 9 -> "9th", 10 -> "10th", 
 11 -> "11th", 12 -> "12th", 13 -> "13th", 14 -> "14th", 15 -> "15th", 
 16 -> "16th", 17 -> "17th", 18 -> "18th", 19 -> "19th", 20 -> "20th", 
 21 -> "21st", 22 -> "22nd", 23 -> "23rd", 24 -> "24th", 25 -> "25th", 
 26 -> "26th", 27 -> "27th", 28 -> "28th", 29 -> "29th", 30 -> "30th", 
 31 -> "31st", 32 -> "32nd", 33 -> "33rd", 34 -> "34th", 35 -> "35th", 
 36 -> "36th", 37 -> "37th", 38 -> "38th", 39 -> "39th", 40 -> "40th", 
 ...
 91 -> "91st", 92 -> "92nd", 93 -> "93rd", 94 -> "94th", 95 -> "95th", 
 96 -> "96th", 97 -> "97th", 98 -> "98th", 99 -> "99th", 100 -> "100th", 
 101 -> "101st", 102 -> "102nd", 103 -> "103rd", 104 -> "104th", 105 -> "105th", 
 106 -> "106th", 107 -> "107th", 108 -> "108th", 109 -> "109th", 110 -> "110th", 
 111 -> "111th", 112 -> "112th",  113 -> "113th", 114 -> "114th", 115 -> "115th", 
 116 -> "116th", 117 -> "117th", 118 -> "118th", 119 -> "119th", 120 -> "120th", 
 121 -> "121st", 122 -> "122nd", 123 -> "123rd", 124 -> "124th", 125 -> "125th", 
 126 -> "126th", 127 -> "127th", 128 -> "128th", 129 -> "129th", 130 -> "130th"}
$\endgroup$
1
  • $\begingroup$ Ah, <> for string concatenation! I forgot it and just used templating. :) $\endgroup$
    – kirma
    Nov 4, 2014 at 19:24
5
$\begingroup$

Short answer: As of Version 10 there is IntegerName, but it has to be slightly modified to be really helpful for ordinal numbers.

Building a converter function

IntegerName allows to provide qualifiers and combinations of these. Unfortunately, using "Ordinal" will not give a correctly spelled result since there is no "and" as in "one hundred and first" (cf. here).

Furthermore, you cannot obtain a "DigitsWords"-like output (e.g. "101st" ) for ordinal numbers by doing IntegerName[ 101, {"DigitsWords", "Ordinal" }] or IntegerName[ 101, {"Ordinal", "DigitsWords" } ] even though the documentation looks like combining qualifiers for IntegerName in this way should be possible.

But tweaking IntegerName slightly within a custom build converter function will get us what we want:

IntegerNameOrdinal[ i_Integer ] := With[
    { 
       stringPatternTwoDigits = IntegerName[ Range[1, 99], "Ordinal"]
    }   
    ,
    IntegerName[ i, "Ordinal" ] // StringReplace[
      ( a__ /; StringFree[a, "-"] ) ~~ b:stringPatternTwoDigits :> a ~~ "and " ~~ b
    ]
]

IntegerNameOrdinal[ i_Integer, "DigitsWords" ] := Module[
    {
        strEnding = IntegerName[ i, "Ordinal" ] // StringTake[ # ,-2 ]&,
        strDigits = ToString @ i
    }
    ,
    strDigits ~~ strEnding
]

IntegerNameOrdinal[ i_Integer, ___ ] := IntegerNameOrdinal[ i ]

Now, we get:

IntegerNameOrdinal[ 2000232, "Words" ] (* or simply: IntegerNameOrdinal[ 2000232 ] *)

"two million two hundred and thirty-second"

IntegerNameOrdinal[ #, "DigitsWords" ]& /@ Range[ 100 ]

{"1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th", "30th", "31st", "32nd", "33rd", "34th", "35th", "36th", "37th", "38th", "39th", "40th", "41st", "42nd", "43rd", "44th", "45th", "46th", "47th", "48th", "49th", "50th", "51st", "52nd", "53rd", "54th", "55th", "56th", "57th", "58th", "59th", "60th", "61st", "62nd", "63rd", "64th", "65th", "66th", "67th", "68th", "69th", "70th", "71st", "72nd", "73rd", "74th", "75th", "76th", "77th", "78th", "79th", "80th", "81st", "82nd", "83rd", "84th", "85th", "86th", "87th", "88th", "89th", "90th", "91st", "92nd", "93rd", "94th", "95th", "96th", "97th", "98th", "99th", "100th"}

$\endgroup$
4
$\begingroup$
ordinalizeF = Module[{mod = Boole[!MemberQ[{11, 12}, Mod[Abs[#], 100]] ] Mod[Abs[#], 10]}, 
   With[{suffix = Switch[mod, 1, "st", 2, "nd", 3, "rd", _, "th"]}, Row[{ToString@#, suffix}]]] &;

ordinalizeF /@ {1, 2, 3, 11, -12, 21, 33, 5542}
(* {1st, 2nd, 3rd, 11th, -12th, 21st, 33rd, 5542nd} *)

Thread[# -> ordinalizeF /@ #] &@{1, 2, 3, 11, -12, 21, 33, 542}
(*{1 -> 1st, 2 -> 2nd, 3 -> 3rd, 11 -> 11th, -12 -> -12th, 21 -> 21st, 33 -> 33rd, 542-> 542nd} *)
$\endgroup$
0
$\begingroup$

The IntegerName function was upgraded in 2017 with version 11.1 according to the doc page.

So here is an implementation for ordinal numbers as requested in the OP.


For languages where gender affects grammatical units, example usage is as follows:

IntegerName[#, {"Italian", "Ordinal", "Feminine"}] & /@ Range[0, 10]

{"zeresima", "prima", "seconda", "terza", "quarta", "quinta",
"sesta", "settima", "ottava", "nona", "decima"}

IntegerName[#, {"Italian", "Ordinal", "Masculine"}] & /@ Range[0, 10]

{"zeresimo", "primo", "secondo", "terzo", "quarto", "quinto",
"sesto", "settimo", "ottavo", "nono", "decimo"}


For English, the usage is simpler.

IntegerName[#, "Ordinal"] & /@ Catenate[{Range[0, 20],
   Range[30, 100, 10]}]

{"zeroth", "first", "second", "third", "fourth", "fifth", "sixth",
"seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth",
"thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth",
"eighteenth", "nineteenth", "twentieth", "thirtieth", "fortieth",
"fiftieth", "sixtieth", "seventieth", "eightieth", "ninetieth", "one
hundredth"}

To use the shorter form such as 1st, 2nd etc., the last two letters can be extracted from the above using StringTake.

StringJoin @@@ ({IntegerString[#], 
     StringTake[IntegerName[#, "Ordinal"], -2]} & /@ Range[0, 100])

{"0th", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th",
"9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th",
"17th", "18th", "19th", "20th", "21st", "22nd", "23rd", "24th",
"25th", "26th", "27th", "28th", "29th", "30th", "31st", "32nd",
"33rd", "34th", "35th", "36th", "37th", "38th", "39th", "40th",
"41st", "42nd", "43rd", "44th", "45th", "46th", "47th", "48th",
"49th", "50th", "51st", "52nd", "53rd", "54th", "55th", "56th",
"57th", "58th", "59th", "60th", "61st", "62nd", "63rd", "64th",
"65th", "66th", "67th", "68th", "69th", "70th", "71st", "72nd",
"73rd", "74th", "75th", "76th", "77th", "78th", "79th", "80th",
"81st", "82nd", "83rd", "84th", "85th", "86th", "87th", "88th",
"89th", "90th", "91st", "92nd", "93rd", "94th", "95th", "96th",
"97th", "98th", "99th", "100th"}


The resource function OrdinalNumberString from this page is available, but using it hanged my system. I am using v12.2.0-Win7-x64.


$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.