Converting Numbers to Words in Perl

I wrote a Perl module called Text::numbers that converts numbers into English words. I have been using it for over a year and I am now getting it ready to upload to CPAN.

The idea is straightforward. You give it a number like 9872334 and it returns “nine million, eight hundred seventy-two thousand, three hundred thirty-four.” It handles negatives, zero, and numbers up to the quadrillions. The whole module is less than a hundred lines of code.

Here is how you use it:

use Text::numbers ;

print Text::numbers::as_words (9872334) ;

# prints: nine million, eight hundred seventy-two thousand,
#         three hundred thirty-four

I built this because I kept running into situations where I needed to display a number in words on a web page or in generated text. Checks, invoices, reports where the number also appears written out. It is the kind of thing you do not want to rewrite every time you need it.

The core of the module works by decomposing the number into groups of three digits (ones, thousands, millions, and so on) and converting each group separately. The tricky parts are the teens (eleven through nineteen do not follow the same pattern as the rest) and getting the commas and spaces right between groups. Here is the main logic:

my @ONES_AND_TEENS = ( '', qw (
one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen
sixteen seventeen eighteen nineteen
) ) ;

my @TENS = qw (
twenty thirty forty fifty sixty seventy eighty ninety
) ;

my @POWERS_OF_TEN = qw (
thousand million billion trillion quadrillion quintillion
) ;

The module walks through powers of ten from the largest down to the smallest, pulling out hundreds, tens, and ones at each level. For each group, it figures out the hundreds digit, the tens, and the ones, then assembles them with the right words and punctuation.

One thing I want to add is support for different languages and number systems. In Indian English, for example, the grouping is different. After thousands, Indian counting uses lakhs (100,000) and crores (10,000,000) instead of the Western hundred thousands and millions. So 10,000,000 in American English is “ten million” but in Indian English it is “one crore.” I grew up using both systems and I would like the module to handle either one.

I am also planning to add the reverse operation, converting words back into numbers. That is a harder problem because of the ambiguity in natural language, but it would make the module much more useful for parsing text.

The source code and a demo are on my site at rajiv.com/work/free/. I plan to upload it to CPAN by the end of this month with proper documentation and packaging. If you write Perl and could use this, try it out and let me know if you find any problems.

Here is the full source code for the Text::numbers module:

package Text::numbers ;

# Developed by Rajiv Pant (Betul)  [email protected]   http://rajiv.org/


use strict ;
use integer ;



$Text::numbers::revision = '$Id: numbers_as_words.pm,v 1.0 1997/09/13 16:51:10 betul Exp betul $';

$Text::numbers::VERSION = '1.0' ;

'version' => <<'END_OF_FUNC',
sub version { return $VERSION ; }
END_OF_FUNC

$Text::numbers::AUTHOR = 'Rajiv Pant (Betul)  [email protected]   http://rajiv.org/' ;



sub as_words
{


my $NEGATIVE = 'negative' ;

# TO DO: In some locales, the term used is "minus".
#	As in "minus two point three degrees centigrade".


my $ZERO = 'zero' ;

my @ONES_AND_TEENS = ( '', qw (

one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen
sixteen seventeen eighteen nineteen

) ) ;


my @TENS = qw (

twenty thirty forty fifty sixty seventy eighty ninety

) ;


my $HUNDRED = 'hundred' ;

my @POWERS_OF_TEN = qw (

thousand million billion trillion quadrillion quintillion

) ;



my $number = shift ;


if ($number == 0)
{
	return $ZERO ;
}


my $result = '' ;


if ($number < 0)
{
	$result .= $NEGATIVE . ' ' ;
	$number = - $number ;
}


my $place = log10 ($number) / 3 ;


while ($place >= 0)
{

	my $h = ten_raised_to_power ($place * 3) ;

	my $hundreds = $number / $h ;

	if ($hundreds != 0)
	{
		$number -= $h * $hundreds ;

		$h = $hundreds / 100 ;

		if ($h != 0)
		{
			$hundreds -= $h * 100 ;
			$result .= $ONES_AND_TEENS[$h] . ' ' . $HUNDRED ;

		} # end if

		if ($hundreds != 0)
		{
			if ($h != 0) { $result .= ' ' } ;

			if ($hundreds < 20)
			{
				$result .= $ONES_AND_TEENS[$hundreds] ;

			} # end if ; begin else

			else
			{
				$h = $hundreds / 10 ;

				if ($h != 0)
				{
					$hundreds -= $h * 10 ;
					$result .= $TENS[$h - 2] ;


				} # end if

				if ($hundreds != 0)
				{
					$result .= '-' . $ONES_AND_TEENS[$hundreds] ;

				} # end if

			} # end else

		} # end if



		if ($place != 0)
		{
			$result .= ' ' . $POWERS_OF_TEN[$place - 1] ;

			if ($number != 0) { $result .= ', ' ; }

		} # end if

	} # end if

	--$place ;

} # end while

return $result ;

} # end sub as_words






# Some Mathematical Functions


sub ten_raised_to_power
{
	my $power = shift ;
	my $result ;

	for ($result = 1 ; --$power >= 0 ; $result *= 10) { ; }

	return $result ;

} # end sub ten_raised_to_power



# Returns the log to base 10 of the given number ;

sub log10
{
	my $given_number = shift ;
	my $power ;

	for ($power = 9 ; ( $given_number / ten_raised_to_power($power) ) == 0 ;
		--$power ) { ; }

	return $power ;

} # end sub log10



1 ; # To make the package return a true value.