...

View Full Version : Function to convert number to ordinal word



chump2877
10-25-2009, 06:15 AM
I'm looking for a way to convert a number to an ordinal word, i.e.:

1 --> first
2 --> second
11 --> eleventh
12 --> twelfth
20 --> twentieth
21 --> twenty-first
22 --> twenty-second

...etc., .etc...

Any ideas? Thanks.

mlseim
10-25-2009, 06:06 PM
I've seen several scripts like this:
http://www.phpro.org/examples/Convert-Numbers-to-Words.html

I wonder if there might be a built-in PHP function for that ...
haven't found it yet though.

chump2877
10-25-2009, 08:37 PM
I found that script also when doing a Google search...but that script doesn't convert to the "ordinal word" equivalent -- just the "word" equivalent...

The single (and I stress "single") script that I did find that attempted to do this (via Google) is so littered with errors that I didn't want to attempt to fix it without first looking for a simpler solution. Looks like I might have to try to fix it though....

A built in PHP function would be great, but I didn't spot one either...

Fou-Lu
10-25-2009, 09:12 PM
Although limited in the size (number to string needs to be manually controlled at each 10 based exponential value), the ordinal is actually the easier part of it.

Ordinal only applies on the 10's and 1's digits. So thats a plus. Ordinals for 1, 2, and 3 are st, nd, and rd. All others between 0 and 9 inclusive are th (you'll need to decide the actual '0' value as either 'zero' or 'zeroth'; we'd need our grammer nazi to verify which are valid).
The leading digits are irrelevant with the exception being 10 through 19 which all have th ordinals (and generally need to be accommodated for particular rulesets - the alorigthm to convert 27 to 'twenty-seventh' is not the same as to convert 17 to 'seventeenth').

Lamped
10-26-2009, 03:30 AM
You caught my imagination, and I'm now absolutely determined to do this!

I'm going to bed now, at 1:30am... Here's what I've come up with so far:



error_reporting(E_ALL);

/*

I think the best way to do this, is to create an array grouped by thousands,
cardinal style, and lookup/modify for ordinal style.

*/

class shortScale {
// Source: Wikipedia (http://en.wikipedia.org/wiki/Names_of_large_numbers)
private static $scale = array('', 'thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'octillion', 'nonillion', 'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion', 'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'noverndecillion', 'vigintillion');
private static $digit = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
private static $digith = array('', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fiftheenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth');
private static $ten = array('', '', 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
private static $tenth = array('', '', 'twentieth', 'thirtieth', 'fortieth', 'fiftieth', 'sixtieth', 'seventieth', 'eightieth', 'ninetieth');

private static function floatToArray($number, &$int, &$frac) {
// Forced $number as (string), effectively to avoid (float) inprecision
@list(, $frac) = explode('.', $number);
$int = explode(',', number_format(ltrim($number, '0'), 0, '', ','));
}

private static function thousandToEnglish($number) {
// Gets numbers from 0 to 999 and returns the cardinal English
}

public static function cardinalToOrdinal($cardinal) {
// Finds the last word in the cardinal arrays and replaces it with
// the entry from the ordinal arrays, or appends "th"
$words = explode(' ', $cardinal);
$last = &$words[count($words)-1];
if (in_array($last, self::$digit)) {
$last = self::$digith[array_search($last, self::$digit)];
} elseif (in_array($last, self::$ten)) {
$last = self::$tenth[array_search($last, self::$ten)];
} elseif (substr($last, -2) != 'th') {
$last .= 'th';
}
return implode(' ', $words);
}

public static function toOrdinal($number) {
// Converts a xth format number to English. e.g. 22nd to twenty-second.
return self::cardinalToOrdinal(self::toCardinal($number));
}

public static function toCardinal($number) {
// Converts a number to English. e.g. 22 to twenty-two.
self::floatToArray($number, $int, $frac);
for($i=count($int)-1; $i>-1; $i--) {
echo($int[$i]."\n");
// use thousandToEnglish to get an array per $int array item and combine them with self::scale[$i]
}
echo(implode(',',$int)." and $frac\n");
}
}


echo(shortScale::toOrdinal(12123456789.123));

chump2877
10-26-2009, 06:26 AM
Thanks to everyone's input on this topic, especially ComputerX who obviously spent some time thinking about how to solve this...:thumbsup:

I used a conglomerate of code from different places (including ComputerX's) to come up with a class that will serve my purpose for now: Get the ordinal phrases for 1 to 999. It could be done better, but it's all I really have patience for right now...:rolleyes:

Feel free to improve on the code if you like...


<?
class Number
{
// Private Fields
private $_digit = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
private $_digith = array('', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth');
private $_ten = array('', '', 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
private $_tenth = array('', '', 'twentieth', 'thirtieth', 'fortieth', 'fiftieth', 'sixtieth', 'seventieth', 'eightieth', 'ninetieth');

// Constants
const _HUNDRED = 'hundred';
const _MAXNUMLENGTH = 3;

// Public Methods
public function toOrdinal($number)
{
return $this->cardinalToOrdinal($this->toCardinal($number));
}

// Private "Helper" Methods
private function cardinalToOrdinal($cardinal)
{
// Finds the last word in the cardinal arrays and replaces it with
// the entry from the ordinal arrays, or appends "th"
$words = explode(' ', $cardinal);
$last = $words[count($words)-1];
$last_parts = explode('-', $last);
$last_part = $last_parts[count($last_parts)-1];

if (in_array($last_part, $this->_digit))
{
$last_part = $this->_digith[array_search($last_part, $this->_digit)];
}
elseif (in_array($last_part, $this->_ten))
{
$last_part = $this->_tenth[array_search($last_part, $this->_ten)];
}
elseif ($last_part == self::_HUNDRED)
{
$last_part = self::_HUNDRED.'th';
}
$last_parts[count($last_parts)-1] = $last_part;
$words[count($words)-1] = implode('-', $last_parts);
return implode(' ', $words);
}

private function toCardinal($number)
{
if ($number < 1 || $number > (int)(str_repeat('9', self::_MAXNUMLENGTH)))
{
throw new Exception("Number is out of range");
}

$Hn = floor($number / 100); /* Hundreds */
$number -= $Hn * 100;
$Dn = floor($number / 10); /* Tens */
$n = $number % 10; /* Ones */

$res = "";

if ($Hn)
{
$res .= (empty($res) ? "" : " ") .
$this->toCardinal($Hn) . " " . self::_HUNDRED;
}

if ($Dn || $n)
{
if (!empty($res))
{
$res .= " and ";
}

if ($Dn < 2)
{
$res .= $this->_digit[$Dn * 10 + $n];
}
else
{
$res .= $this->_ten[$Dn];

if ($n)
{
$res .= "-" . $this->_digit[$n];
}
}
}

return $res;
}
}

$num = new Number;
for ($i=1; $i<2000; $i++)
{
try
{
echo $num->toOrdinal($i) . '<br />';
}
catch(Exception $ex)
{
die($ex->getMessage());
}
}
?>

Lamped
10-26-2009, 02:26 PM
I'm really pleased you got something working. I've continued working on it for the sake of completion, and that I like having useful code snippets.

This example is perfect for numbers from 0 to 999, with an example of cardinal and ordinal useage. I don't put a hyphen in, as it requires extra processing from the cardinalToOrdinal function that I don't personally think is necessary.

I'll put in the scaling and update here, again, for completeness sake.



error_reporting(E_ALL);


class shortScale {
// Source: Wikipedia (http://en.wikipedia.org/wiki/Names_of_large_numbers)
private static $scale = array('', 'thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'octillion', 'nonillion', 'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion', 'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'noverndecillion', 'vigintillion');
private static $digit = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
private static $digith = array('', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fiftheenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth');
private static $ten = array('', '', 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
private static $tenth = array('', '', 'twentieth', 'thirtieth', 'fortieth', 'fiftieth', 'sixtieth', 'seventieth', 'eightieth', 'ninetieth');

private static function floatToArray($number, &$int, &$frac) {
// Forced $number as (string), effectively to avoid (float) inprecision
@list(, $frac) = explode('.', $number);
$int = explode(',', number_format(ltrim($number, '0'), 0, '', ','));
}

static function thousandToEnglish($number) {
// Gets numbers from 0 to 999 and returns the cardinal English
$hundreds = floor($number / 100);
$tens = $number % 100;
$pre = ($hundreds ? self::$digit[$hundreds].' hundred' : '');
if ($tens < 20)
$post = self::$digit[$tens];
else
$post = trim(self::$ten[floor($tens / 10)].' '.self::$digit[$tens % 10]);
if ($pre && $post) return $pre.' and '.$post;
return $pre.$post;
}

public static function cardinalToOrdinal($cardinal) {
// Finds the last word in the cardinal arrays and replaces it with
// the entry from the ordinal arrays, or appends "th"
$words = explode(' ', $cardinal);
$last = &$words[count($words)-1];
if (in_array($last, self::$digit)) {
$last = self::$digith[array_search($last, self::$digit)];
} elseif (in_array($last, self::$ten)) {
$last = self::$tenth[array_search($last, self::$ten)];
} elseif (substr($last, -2) != 'th') {
$last .= 'th';
}
return implode(' ', $words);
}

public static function toOrdinal($number) {
// Converts a xth format number to English. e.g. 22nd to twenty-second.
return self::cardinalToOrdinal(self::toCardinal($number));
}

public static function toCardinal($number) {
// Converts a number to English. e.g. 22 to twenty-two.
self::floatToArray($number, $int, $frac);
for($i=count($int)-1; $i>-1; $i--) {
echo($int[$i]."\n");
// use thousandToEnglish to get an array per $int array item and combine them with self::scale[$i]
}
echo(implode(',',$int)." and $frac\n");
}
}
for ($i=0; $i<20; $i++) {
$r = rand(0,999);
$cardinal = shortScale::thousandToEnglish($r);
echo($r.': '.$cardinal.' -- '.shortScale::cardinalToOrdinal($cardinal)."\n");
}

Lamped
10-26-2009, 03:33 PM
Right, I'm done.

Did you know that 13,052,149,762,014,642,176,925,499,392 is thirteen nonillion, fifty two octillion, one hundred and fourty nine sextillion, seven hundred and sixty two quintillion, fourteen quadrillion, six hundred and fourty two trillion, one hundred and seventy six billion, nine hundred and twenty five million, four hundred and ninety nine thousand and three hundred and ninety two?

Use:


$english = shortScale::toOrdinal($number); // 2 to second
$english = shortScale::toCardinal($number); // 2 to two

The class:

class shortScale {
// Source: Wikipedia (http://en.wikipedia.org/wiki/Names_of_large_numbers)
private static $scale = array('', 'thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'octillion', 'nonillion', 'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion', 'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'noverndecillion', 'vigintillion');
private static $digit = array('', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
private static $digith = array('', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fiftheenth', 'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth');
private static $ten = array('', '', 'twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
private static $tenth = array('', '', 'twentieth', 'thirtieth', 'fortieth', 'fiftieth', 'sixtieth', 'seventieth', 'eightieth', 'ninetieth');

private static function floatToArray($number, &$int, &$frac) {
// Forced $number as (string), effectively to avoid (float) inprecision
@list(, $frac) = explode('.', $number);
if ($frac || !is_numeric($number) || (strlen($number) > 60)) throw new Exception('Not a number or not a supported number type');
// $int = explode(',', number_format(ltrim($number, '0'), 0, '', ',')); -- Buggy
$int = str_split(str_pad($number, ceil(strlen($number)/3)*3, '0', STR_PAD_LEFT), 3);
}

private static function thousandToEnglish($number) {
// Gets numbers from 0 to 999 and returns the cardinal English
$hundreds = floor($number / 100);
$tens = $number % 100;
$pre = ($hundreds ? self::$digit[$hundreds].' hundred' : '');
if ($tens < 20)
$post = self::$digit[$tens];
else
$post = trim(self::$ten[floor($tens / 10)].' '.self::$digit[$tens % 10]);
if ($pre && $post) return $pre.' and '.$post;
return $pre.$post;
}

private static function cardinalToOrdinal($cardinal) {
// Finds the last word in the cardinal arrays and replaces it with
// the entry from the ordinal arrays, or appends "th"
$words = explode(' ', $cardinal);
$last = &$words[count($words)-1];
if (in_array($last, self::$digit)) {
$last = self::$digith[array_search($last, self::$digit)];
} elseif (in_array($last, self::$ten)) {
$last = self::$tenth[array_search($last, self::$ten)];
} elseif (substr($last, -2) != 'th') {
$last .= 'th';
}
return implode(' ', $words);
}

public static function toOrdinal($number) {
// Converts a xth format number to English. e.g. 22nd to twenty-second.
return trim(self::cardinalToOrdinal(self::toCardinal($number)));
}

public static function toCardinal($number) {
// Converts a number to English. e.g. 22 to twenty-two.
self::floatToArray($number, $int, $frac);
$int = array_reverse($int);
for($i=count($int)-1; $i>-1; $i--) {
$englishnumber = self::thousandToEnglish($int[$i]);
if ($englishnumber)
$english[] = $englishnumber.' '.self::$scale[$i];
}
$post = array_pop($english);
$pre = implode(', ', $english);
if ($pre && $post) return trim($pre.' and '.$post);
return trim($pre.$post);
}
}

Extreme example:

for ($i=0; $i<20; $i++) {
$a = rand(0,PHP_INT_MAX);
$b = rand(0,PHP_INT_MAX);
$c = rand(0,PHP_INT_MAX);
$huge = $a.$b.$c;
echo($huge."\n".shortScale::toOrdinal($huge)."\n\n");
}

Let me know if you find a bug. If you don't like the spelling of my native English fingers, change the arrays yourself! :p

Edit: Bug fix already: Now supports numbers like one billion and one, and reports an error on numbers over 60 digits.

mOrloff
02-28-2013, 11:20 PM
SWEET!!!
Regardless of the fact that this thread is almost 4 years old, it saved me some real headache (and time).

I needed to build a number-word "dictionary", and with this class, I was able to bust that out in nothing flat =D
Since I needed to include the shortened versions of ordinals too, I added this method.
Hope someone else finds it useful.


public static function toShortOrdinal($number) {
// Converts a number to the shortened English (alpha-numeric) ordinal. e.g. 23 to 23rd.
return $number . substr(self::cardinalToOrdinal(self::toCardinal($number)),-2);
}


Also, in the comment for toOrdinal(), it ways that it "Converts a xth format number to English. e.g. 22nd to twenty-second", but when I tested shortScale::toOrdinal('3rd'), it triggered the "Not a number or not a supported number type" error.
Am I missing something???

~ Mo



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum