Originally posted by coditoergosum
Have you thought of any practical uses for it?
Yeah; chop out any bits you don't need and you've got a decimal integer → English name converter that (I hope) can be used in a live environment; hence the splitting of each degree of complication into separate functions.
You probably don't need all 3 (three) nomenclatures, and you might never have to deal with numbers above 232 -1 (four billion two hundred ninety-four million nine hundred sixty-seven thousand two hundred ninety-five), or even much beyond 1000 (one thousand) or so.
For example, allowing only numbers up to 999,999,999,999,999,999,999,999,999,999,999 (one decillion minus one) in American nomenclature, and inlining triplets() and zillion(), gives:
function num2name($number)
{
if($number==0) return 'zero';
// Break the number into triples (counting from the right)
$number = explode(' ',strrev(chunk_split(strrev($number),3,' ')));
array_shift($number);
// So that lower-placed triples in the number
// are given lower indices in the array.
// This means we can insert "million" "billion" etc. while counting
// forward instead of backward.
$number = array_reverse($number);
// Turn '123' into 'one hundred and twenty three'
$digs = array('zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine');
$tens = array('zeroty', 'onety', 'twenty', 'thirty', 'forty',
'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
$teens = array('ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
foreach($number as $i => $v)
{
$num = str_pad($v, 3, '0', STR_PAD_LEFT);
list($h,$t,$u) = sscanf($num, '%1d%1d%1d');
$name = '';
if($h>0) $name = $digs[$h].' hundred '.$name;
if($t == 1)
$name .= $teens[$u];
else
{
if($t != 0)
$name .= $tens[$t].'-';
$name .= $digs[$u];
}
$name = str_replace('zero','',str_replace('-zero','',$name));
$number[$i] = $name;
}
if(count($number)>1)
{
if($number[1] != '')
$number[1] .= ' thousand '.$number[0];
else
$number[1] = $number[0];
unset($number[0]);
}
// Tidy the array indices.
$number = array_values($number);
// Add zillion names
$count = count($number);
if($count>10)
{ trigger_error('Number too large (>=10^'.(3*$i+3).')', E_USER_WARNING);
return false;
}
// Chuquet's words
$i_below_10 = array('', 'million', 'billion','trillion',
'quadrillion','quintillion', 'sextillion',
'septillion', 'octillion','nonillion');
for($i = 1; $i<$count; ++$i)
{
if($number[$i]=='') continue;
$zillion = $i_below_10[$i];
$number[$i] .= ' '.$zillion;
}
// Back around the other way again
$number = array_reverse($number);
$number = join(' ',$number);
// Tidy up loose spaces.
$number = preg_replace('/ $/', '', preg_replace('/ +/',' ',$number));
return $number;
}
Which could do with better variable names, but I just shoved the functions in there without considering that.
Cutting it down even further; here's a version that only handles integers <one million (further simplifications are now possible, and left as exercises for the reader):
function num2name($number)
{
if($number==0) return 'zero';
// Break the number into triples (counting from the right)
$number = explode(' ',strrev(chunk_split(strrev($number),3,' ')));
array_shift($number);
// So that lower-placed triples in the number
// are given lower indices in the array.
// This means we can insert "million" "billion" etc. while counting
// forward instead of backward.
$number = array_reverse($number);
// Turn '123' into 'one hundred and twenty three'
$digs = array('zero', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine');
$tens = array('zeroty', 'onety', 'twenty', 'thirty', 'forty',
'fifty', 'sixty', 'seventy', 'eighty', 'ninety');
$teens = array('ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen');
foreach($number as $i => $v)
{
$num = str_pad($v, 3, '0', STR_PAD_LEFT);
list($h,$t,$u) = sscanf($num, '%1d%1d%1d');
$name = '';
if($h>0) $name = $digs[$h].' hundred '.$name;
if($t == 1)
$name .= $teens[$u];
else
{
if($t != 0)
$name .= $tens[$t].'-';
$name .= $digs[$u];
}
$name = str_replace('zero','',str_replace('-zero','',$name));
$number[$i] = $name;
}
if(count($number)>1)
{
if($number[1] != '')
$number[1] .= ' thousand '.$number[0];
else
$number[1] = $number[0];
unset($number[0]);
}
// Tidy the array indices.
$number = array_values($number);
// Add zillion names
$count = count($number);
if($count>1)
{ trigger_error('Number too large (>=10^6)', E_USER_WARNING);
return false;
}
// Back around the other way again
$number = array_reverse($number);
$number = join(' ',$number);
// Tidy up loose spaces.
$number = preg_replace('/ $/', '', preg_replace('/ +/',' ',$number));
return $number;
}