Ordinal Suffix Tools
String getOrdinalSuffix(unsigned int pVal)
{
unsigned int last2Digits= pVal % 100, lastDigit = pVal % 10;
return g_suffixes[(last2Digits-lastDigit) == 10 ? 0 :
lastDigit > 3 ? 0 : lastDigit];
};
String remOrdinalSuffix(String pText)
{
String l_substr;
pText = trim(pText);
if( pText.length() <= 2 )
return "";
l_substr = toLowerCase(pText.substr( pText.length() - 2 ));
if( l_substr.find("st") != String::npos ||
l_substr.find("nd") != String::npos ||
l_substr.find("rd") != String::npos ||
l_substr.find("th") != String::npos )
{
pText = pText.substr( 0, pText.length() - 2 );
}
return pText;
};
Anyway, this piece of code will turn any unsigned integer into a string of the form '1st', '2nd', '3rd', '4th', etc... This was the thing that got me started. But what if i want to generate the longhand form of an ordinal. Everything becomes so much more complicated. First of all, as you may have noticed i was definitley using a typedef for my string type so let me present the utility functions i needed.
#include <string>
#include <algorithm>
#include <vector>
typedef std::string String;
// Ordinal Suffixes
String g_suffixes[] = { "th", "st", "nd", "rd" };
// Minimal Numeric Units (DIGIT!)
String g_digits[] = { "zero", "one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", "eleven", "twelve",
"thirteen", "fourteen", "fifteen", "sixteen",
"seventeen", "eighteen", "nineteen" };
// What To Call These?
String g_decas[] = { "", "", "twenty", "thirty", "fourty", "fifty",
"sixty", "seventy", "eighty", "ninty" };
// What To Call THESE??
String g_scopes[] = { "", "hundred", "thousand", "million", "billion",
/* YEAH YEAH YEAH, NOT FOR 32 BIT ... */
"trillion", "quadrillion", "sextillion", "septillion"};
String trim( String pTarget, String pDelims = " \n\r\t")
{
String::size_type notwhite;
notwhite = pTarget.find_first_not_of(pDelims);
if( notwhite != String::npos )
pTarget.erase(0, notwhite);
notwhite = pTarget.find_last_not_of(pDelims);
if( notwhite != String::npos )
pTarget.erase(notwhite+1, pTarget.length() - notwhite);
return pTarget;
};
String toLowerCase( String pTarget )
{
std::transform( pTarget.begin(), pTarget.end(),
pTarget.begin(), tolower );
return pTarget;
};
unsigned int power( unsigned int pNumber, unsigned int pScale )
{
unsigned int l_retval;
l_retval = 1;
while( pScale > 0 )
{
l_retval *= pNumber;
pScale--;
}
return l_retval;
};
unsigned int count_digits( unsigned int pNumber )
{
unsigned int l_digits = 0;
while( pNumber > 0 )
{
l_digits++;
pNumber /= 10;
}
return l_digits;
};
unsigned int msd( unsigned int pNumber )
{
unsigned int l_ratio = (power(10, count_digits(pNumber))/10);
while( l_ratio && pNumber % l_ratio )
{
pNumber--;
l_ratio = (power(10, count_digits(pNumber))/10);
}
if( pNumber )
return pNumber / power(10, count_digits(pNumber)-1);
return 0;
};
And so there we have it. trim() extracts any space characters from the beginning or end of the string, i use this when converting from string to int.
toLowerCase() remove all case from a string which therefore allows us to do case insensitive conversions from strings to ints.
power() is my own version of Math::pow() which multiplies an unsigned int x by itself y numbers of times instead of doubles or floats. Note x ^ 0 == 1.
count_digits() determines the exact number of digits that exists in an unsigned int. This can be anywhere from 0 to 10 on a 32 bit system.
msd() retrieves the most significant digit in an unsigned integer which really came in handy when converting to a string.
It was important to decide upon an ordering for the contents of the arrays so that having an index allows us to compute/extract values with as few extra variables as possible.
With the key players in place we can now go on to our implementation. Everything you've seen so far and everything below can be put into one big source file and should be ready to go as i just copied and pasted it. The source file total up to about 13k so any apologies you may wish me to provide are hereby given now.
Also, all of the algorithms provided below will essentially work for an infinite sized number so ports to 64 bit platforms will be much easier. Ports to both C#/Java/Visual Basic and COBOL are in the works as well! For pricing information visit my website at .... *HEH KIDDING*
// Spell Out An Integer
String intToText(unsigned int pNumber)
{
if( pNumber == 0 )
return g_digits[0];
unsigned int l_val = pNumber;
unsigned int l_digits = 1;
String l_retval = "";
while( l_digits )
{
l_digits = count_digits(l_val);
if( !l_digits )
break;
unsigned int l_pow = (((int)((l_digits+2)/3)) * 3) - 3;
unsigned int l_scale = power( 10, l_pow );
unsigned int l_l_val = l_val/l_scale;
unsigned l_dig3 = 0;
if( l_l_val >= 100 )
l_dig3 = msd((l_l_val)%1000);
unsigned l_dig2 = 0;
if( (l_l_val%100) >= 10 )
l_dig2 = msd((l_l_val)%100);
unsigned l_dig1 = msd((l_l_val)%10);
if( l_dig3 )
{
l_retval += g_digits[l_dig3];
l_retval += " ";
l_retval += g_scopes[1];
l_retval += " ";
}
if( l_dig2 )
{
if( l_dig2 == 1 )
l_retval += g_digits[l_dig1+10];
else
l_retval += g_decas[l_dig2];
l_retval += " ";
}
if( l_dig2 != 1 && (l_dig1 != 0 || (l_digits < 3 && !l_dig3 && !l_dig2)) )
{
l_retval +=g_digits[l_dig1];
l_retval += " ";
}
if( l_digits > 3 && (l_dig1 || l_dig2 || l_dig3) )
{
l_retval += g_scopes[(l_digits+2)/3];
l_retval += " ";
}
l_val = l_val % l_scale;
l_digits -= (((l_digits+2)%3)+1);
}
l_retval = l_retval.substr(0, l_retval.length()-1);
return l_retval;
};
We see here part one of our int to ordinal function. This one does the bulk of the work converting each integer into a string like 'one hundred twenty two thousand three hundred and thirty'.
// Changes One to First, etc.. based
// on the last four letters in the string.
String textualNumberToOrdinal( String pString )
{
String l_input;
String l_substr;
String::size_type l_size;
l_input = trim(pString);
l_size = l_input.length();
l_substr = toLowerCase(l_input.substr(l_size > 4 ? l_size - 4 : 0));
if( l_substr.find("one") != String::npos )
{
l_input = l_input.substr(0, l_size-3);
l_input += "first";
}
else if( l_substr.find("two") != String::npos )
{
l_input = l_input.substr(0, l_size-3);
l_input += "second";
}
else if( l_substr.find("hree") != String::npos )
{
l_input = l_input.substr(0, l_size-5);
l_input += "third";
}
else if( l_substr.find("four") != String::npos )
{
l_input = l_input.substr(0, l_size-4);
l_input += "fourth";
}
else if( l_substr.find("five") != String::npos )
{
l_input = l_input.substr(0, l_size-4);
l_input += "fifth";
}
else if( l_substr.find("six") != String::npos )
{
l_input = l_input.substr(0, l_size-3);
l_input += "sixth";
}
else if( l_substr.find("ight") != String::npos )
{
l_input = l_input.substr(0, l_size-5);
l_input += "eighth";
}
else if( l_substr.find("nine") != String::npos )
{
l_input = l_input.substr(0, l_size-4);
l_input += "ninth";
}
else if( l_substr.find("ty") != String::npos )
{
l_input = l_input.substr(0, l_size-1);
l_input += "ieth";
}
else if( l_substr.find("en") != String::npos ||
l_substr.find("elve") != String::npos )
{
l_input += "th";
}
else if( l_substr.find("dred") != String::npos )
{
l_input += "th";
}
else if( l_substr.find("sand") != String::npos )
{
l_input += "th";
}
else if( l_substr.find("lion") != String::npos )
{
l_input += "th";
}
return l_input;
};
Since the function to convert ints to spelled out strings already exists this function just has to change the least significant digit to it's ordinal counterpart.
int textNumberToInt(String pData)
{
String l_input = toLowerCase(pData);
unsigned int l_register = 0;
unsigned int l_retval = 0;
struct ordinalSearch
{
ordinalSearch( String::size_type pIter, unsigned int pIdx, String *pArray )
: m_location(pIter),
m_index(pIdx),
m_array(pArray)
{
};
String::size_type m_location;
unsigned int m_index;
String *m_array;
};
ordinalSearch l_best(String::npos, 0, 0);
int l_digituse = 0,
l_decause = 0,
l_scopeuse = 0;
while( l_input.length() > 0 )
{
l_best = ordinalSearch(String::npos, 0, 0);
l_input = trim(l_input);
std::vector<ordinalSearch> results;
for( unsigned int i = 0 ; i < 20 ; i++ )
{
results.insert( results.begin(),
ordinalSearch( l_input.find(g_digits[i]), i, g_digits ) );
}
for( unsigned int i = 2 ; i < 10 ; i++ )
{
results.insert( results.begin(),
ordinalSearch( l_input.find(g_decas[i]), i, g_decas ) );
}
for( unsigned int i = 1 ; i < 9 ; i++ )
{
results.insert( results.begin(),
ordinalSearch( l_input.find(g_scopes[i]), i, g_scopes ) );
}
for( std::vector<ordinalSearch>::iterator iter = results.begin() ;
iter != results.end() ;
iter++ )
{
if( (*iter).m_location < l_best.m_location )
l_best = (*iter);
}
if( l_best.m_location != 0 || l_best.m_location == String::npos )
throw std::exception("String was not a valid ordinal");
if( l_best.m_array == g_digits )
{
l_register += l_best.m_index;
l_digituse++;
}
else if( l_best.m_array == g_decas )
{
l_register += (l_best.m_index*10);
l_decause++;
}
else if( l_best.m_array == g_scopes )
{
if( l_best.m_index == 1 )
{
l_register *= 100;
l_decause++;
}
else
{
l_scopeuse++;
l_register *= power(1000, l_best.m_index-1);
l_retval += l_register;
l_register = 0;
}
}
///////////////////////////////////////
// ERROR CHECKING -- PROBABLY NOT COMPLETE
//
// WEED OUT PROBLEMS
if( l_digituse > 2 // ONE ONE ONE
|| l_scopeuse > 1 // THOUSAND THOUSAND
|| l_decause > 2 // TWENTY HUNDRED TWENTY
// TWENTY ONE HUNDRED OR ONE TWENTY HUNDRED
|| (l_decause==2 && l_best.m_array==g_decas && l_best.m_index==1)
|| (l_digituse==2 && l_decause<1) ) // ONE ONE
{
throw std::exception("String wasn't a valid format");
}
// RESET IF POSSIBLE
// ONE THOUSAND OR TWENTY THOUSAND BUT NOT THOUSAND THOUSAND
if( l_scopeuse && ( l_digituse || l_decause) )
{ l_digituse = 0; l_decause = 0; l_scopeuse = 0; }
//
// END ERROR CHECKING
///////////////////////////////////////////
l_input = l_input.substr( l_best.m_array[l_best.m_index].length() );
}
if( !(l_best.m_array == g_scopes ) ||
(l_best.m_array == g_scopes && l_best.m_index == 1) )
l_retval += l_register;
return l_retval;
}
This is the most important function in the library and most likely the one which needs the most fixing. It has some basic error checking so if you pass an invalid number in here an exception will be thrown. This error checking probably doesn't handle every case but i've tried a number and they all got caught so, yeah...Notice that it does case insensitive comparisons and converts a string like 'one hundred' into an integer! It's sweet. Also note that 'onehundred' and 'one hundred' will provide the same result. If you added an extra string transform to remove things like hyphens and commas you could parse even more complex strings like 'one,-hundred-'.
String ordinalToTextNumber( String pString )
{
String::size_type l_index;
String l_input = toLowerCase(trim(pString));
if( (l_index = l_input.find("first")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "one";
}
else
if( (l_index = l_input.find("second")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "two";
}
else
if( (l_index = l_input.find("third")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "three";
}
else
if( (l_index = l_input.find("fifth")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "five";
}
else
if( (l_index = l_input.find("eighth")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "eight";
}
else
if( (l_index = l_input.find("ninth")) != String::npos )
{
l_input = l_input.substr(0, l_index);
l_input += "nine";
}
else
if( l_input.find("ieth", l_input.length()-4) != String::npos )
{
l_input = l_input.substr(0, l_input.length()-4);
l_input += "y";
}
else
if( (l_index = l_input.find("th", l_input.length()-2)) != String::npos )
{
l_input = l_input.substr(0, l_input.length()-2);
}
return l_input;
};
This one is my favorite of the whole bunch. It extracts the ordinal from the ordinal string thus converting to a normal spelled out string.
String intToOrdinal( unsigned int val )
{
return textualNumberToOrdinal( intToText(val) );
};
unsigned int ordinalToInt( String pData )
{
return textNumberToInt( ordinalToTextNumber(pData) );
};
These guys are the helper functions which put all of my hard ordinal conversion work to use. Call intToOrdinal() to make a String out of a number and call ordinalToInt() to make a number of a spelled out number. All i need to do now is put it into a header file and make it the ordinal_cast<>()!
unsigned int remNegative( int pVal )
{
return pVal < 0 ? 0 - pVal : pVal;
}
int addNegative( unsigned int pVal )
{
return 0 - ((int)pVal);
}
Yeah yeah, for those of us who must have negative ORDINALS, here's the integer side of things...
String addNegative( String pString )
{
return "negative " + trim(pString);
}
String remNegative( String pString )
{
if( pString.length() < 8 )
pString = trim(pString);
if( toLowerCase(pString).find("negative") == 0 )
return trim(pString.substr(8));
return pString;
}
And here's the string side!I've tested this from 0 to 1 million and a less complete test from 1 million to 4 billion so i'm pretty sure it all works. It's (NOT) as fast as it could be. I really would like to hear comments and possibly get some additional optimizations and help improving the error checking in textNumberToInt().
Oh, here's a little test program you could use to try it out.
void main(void)
{
for( unsigned int i = 0 ; i < 4000000000 ; i++ )
{
String number;
String ordinal;
unsigned int result;
number = intToText(i);
ordinal = textualNumberToOrdinal(number);
result = ordinalToInt(ordinal);
cout << "if (" << i << " == " << number << ") " << std::endl <<
"\t This is the " << ordinal << " correct value printed out of "
<< result <<
"total numbers which have been printed so far" <<
endl;
// AS GOOD AS 'if( false )' >=)
if( result != i )
cout << "betrayal!" << endl;
}
}












