Author: Steven Neiland
Published:

Warning: This blog entry was written two or more years ago. Therefore, it may contain broken links, out-dated or misleading content, or information that is just plain wrong. Please read on with caution.

I would consider this a very simple problem to solve. However after recently coming across a solution for this in the wild that was shall we say less than optimal, I decided that maybe it was worth sharing my solution for any new developers.

The Complete Function

First here is my solution to the problem. See below for a break down.

<cffunction name="ordinalSuffix" access="public" output="false" returntype="string">
      <cfargument name="theNumber" type="numeric" required="true">
      
      <cfset var suffix = "">
      
      <cfif theNumber GTE 10 AND mid(theNumber,len(theNumber)-1,1) EQ "1">
            <cfset suffix = "th">
      <cfelseif theNumber GT 0>
            <cfswitch expression="#right(theNumber,1)#">
                  <cfcase value="1">
                        <cfset suffix = "st">
                  </cfcase>
                  <cfcase value="2">
                        <cfset suffix = "nd">
                  </cfcase>
                  <cfcase value="3">
                        <cfset suffix = "rd">
                  </cfcase>
                  <cfdefaultcase>
                        <cfset suffix = "th">
                  </cfdefaultcase>
            </cfswitch>
      </cfif>
      
      <cfreturn suffix>
</cffunction>

Breaking It Down

So what is it doing here. First we check if the number ends with two digits representing values between "10" and "19" by using the string functions mid() and len() to get the second last digit of the number. If true then the suffix is always "th".

<cfif theNumber GTE 10 AND mid(theNumber,len(theNumber)-1,1) EQ "1">
      <cfset suffix = "th">

For every other number greater than zero we get the last digit of the number using the right() string function.

<cfelseif theNumber GT 0>
      <cfswitch expression="#right(theNumber,1)#">

Based on the value of that last digit we set the suffix accordingly.

            <cfcase value="1">
                  <cfset suffix = "st">
            </cfcase>
            <cfcase value="2">
                  <cfset suffix = "nd">
            </cfcase>
            <cfcase value="3">
                  <cfset suffix = "rd">
            </cfcase>
            <cfdefaultcase>
                  <cfset suffix = "th">
            </cfdefaultcase>
      </cfswitch>
</cfif>

Alternative Solution

Here is an alternative solution as suggestion by Matt Gutting. I tested it against my solution and it does give a small performance increase when you get up to several thousand calls. On a loop of 10,000 I got a difference of 300ms (mine) vs 180ms (matts).

<cffunction name="ordinalSuffix" access="public" output="false" returntype="string">
      <cfargument name="theNumber" type="numeric" required="true">
      
      <cfset var suffix = "">
      
      <cfif int(theNumber / 10) mod 10 is 1>
            <cfset suffix = "th">
      <cfelseif theNumber GT 0>
            <cfswitch expression="#theNumber mod 10#">
                  <cfcase value="1">
                        <cfset suffix = "st">
                  </cfcase>
                  <cfcase value="2">
                        <cfset suffix = "nd">
                  </cfcase>
                  <cfcase value="3">
                        <cfset suffix = "rd">
                  </cfcase>
                  <cfdefaultcase>
                        <cfset suffix = "th">
                  </cfdefaultcase>
            </cfswitch>
      </cfif>
      
      <cfreturn suffix>
</cffunction>

Related Blog Postings

What Do You Think?

Reader Comments

Matt Gutting's Gravatar
Matt Gutting
Wednesday, April 25, 2012 at 10:29:39 PM EDT

Very nice. Two things that I would have done differently, but not necessarily better: First, I think this is one of the few times where I would have used CFScript. Maybe. Second, instead of picking apart theNumber as a string of digits, I would have taken advantage of the fact that it's described in cfargument as a numeric. Thus, to see whether it's between 10 and 19, I would have replaced

<cfif theNumber GTE 10 AND mid(theNumber,len(theNumber)-1,1) EQ "1">

with

<cfif int(theNumber / 10) mod 10 is 1>

and

<cfswitch expression="#right(theNumber,1)#">

Just my approach.
with

<cfswitch expression="#theNumber mod 10#">

Steven Neiland's Gravatar
Steven Neiland
Thursday, April 26, 2012 at 12:43:03 AM EDT

Thanks Matt.

Im not a fan of cfscript in general. I like to keep my code consistent and because I find cfquery clunky in script I am unlikely to switch.

I like your approach to finding the 10-19 range. It does give a small performance boost of around 400ms over a cycle of 10000 permutations. Its small but every bit counts.

The one point in favour of my solution is that it is easy to understand for new developers the logic without having to go into explaining the math.

Post a Comment

Comment Etiquette:

  • Please keep comments on-topic.
  • Please do not post unrelated questions or large chunks of code.
  • Please do not engage in flaming/abusive behaviour.
  • Comments that contain or appear to be advertisments, will not be published.
  • Comments that appear to be created for the purpose of linkbuilding to commercial sites will be removed.

We are all adults here so play nice.

*
*



Archives Blog Listing

Tag Listing

Learn CF In A Week

Treehouse

 
Fork me on GitHub