File Coverage

blib/lib/Lingua/ENG/Numbers.pm
Criterion Covered Total %
statement 135 152 88.8
branch 29 50 58.0
condition 21 43 48.8
subroutine 17 17 100.0
pod 9 9 100.0
total 211 271 77.8


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; mode:folding; coding:utf-8; -*-
2              
3             package Lingua::ENG::Numbers;
4             # ABSTRACT: Number 2 word conversion for ENG.
5              
6             # {{{ use block
7              
8 1     1   141071 use 5.10.1;
  1         4  
  1         59  
9 1     1   5 use strict;
  1         2  
  1         38  
10 1     1   4 use warnings;
  1         6  
  1         29  
11 1     1   5 use base qw(Exporter);
  1         1  
  1         295  
12 1     1   6 use Carp;
  1         2  
  1         117  
13              
14 1         758 use vars qw(
15             @EXPORT_OK
16             $VERSION
17              
18             $MODE
19              
20             %INPUT_GROUP_DELIMITER
21             %INPUT_DECIMAL_DELIMITER
22             %OUTPUT_BLOCK_DELIMITER
23             %OUTPUT_GROUP_DELIMITER
24             %OUTPUT_NUMBER_DELIMITER
25             %OUTPUT_DECIMAL_DELIMITER
26              
27             %NUMBER_NAMES
28             %SIGN_NAMES
29              
30             $TRUE
31             $FALSE
32             $SIGN_POSITIVE
33             $SIGN_NEGATIVE
34 1     1   6 );
  1         2  
35              
36             # }}}
37             # {{{ variables declaration
38              
39             our $VERSION = 0.1106;
40              
41             BEGIN {
42              
43             # Exporter Stuff
44 1     1   3 @EXPORT_OK = qw(American British);
45              
46             # Constants
47 1         3 $TRUE = 1;
48 1         2 $FALSE = 0;
49 1         2 $SIGN_POSITIVE = 1;
50 1         1 $SIGN_NEGATIVE = -1;
51              
52             # Default Mode
53 1         2 $MODE = "American";
54              
55             # Delimiters
56 1         3 %OUTPUT_NUMBER_DELIMITER
57             = (
58             'American' => '-',
59             'British' => '-'
60             );
61              
62 1         10 %OUTPUT_GROUP_DELIMITER = (
63             'American' => ' ',
64             'British' => ' '
65             );
66              
67 1         3 %OUTPUT_BLOCK_DELIMITER = (
68             'American' => ', ',
69             'British' => ', '
70             );
71              
72 1         2 %OUTPUT_DECIMAL_DELIMITER = (
73             'American' => 'point ',
74             'British' => 'point '
75             );
76              
77              
78 1         3 %INPUT_GROUP_DELIMITER = (
79             'American' => ',',
80             'British' => '.'
81             );
82              
83 1         2 %INPUT_DECIMAL_DELIMITER = (
84             'American' => '.',
85             'British' => ','
86             );
87              
88              
89             # Low-Level Names
90 1         82 %SIGN_NAMES = (
91             'American' => {
92             $SIGN_POSITIVE => '',
93             $SIGN_NEGATIVE => 'Negative'
94             },
95             'British' => {
96             $SIGN_POSITIVE => '',
97             $SIGN_NEGATIVE => 'Negative'
98             }
99             );
100              
101 1         1892 %NUMBER_NAMES = (
102             'American' => {
103             0 => 'Zero',
104             1 => 'One',
105             2 => 'Two',
106             3 => 'Three',
107             4 => 'Four',
108             5 => 'Five',
109             6 => 'Six',
110             7 => 'Seven',
111             8 => 'Eight',
112             9 => 'Nine',
113             10 => 'Ten',
114             11 => 'Eleven',
115             12 => 'Twelve',
116             13 => 'Thirteen',
117             14 => 'Fourteen',
118             15 => 'Fifteen',
119             16 => 'Sixteen',
120             17 => 'Seventeen',
121             18 => 'Eighteen',
122             19 => 'Nineteen',
123             20 => 'Twenty',
124             30 => 'Thirty',
125             40 => 'Fourty',
126             50 => 'Fifty',
127             60 => 'Sixty',
128             70 => 'Seventy',
129             80 => 'Eighty',
130             90 => 'Ninety',
131             10**2 => 'Hundred',
132             10**3 => 'Thousand',
133             10**6 => 'Million',
134             10**9 => 'Billion',
135             10**12 => 'Trillion',
136             10**15 => 'Quadrillion',
137             10**18 => 'Quintillion',
138             10**21 => 'Sextillion',
139             10**24 => 'Septillion',
140             10**27 => 'Octillion',
141             10**30 => 'Nonillian',
142             10**33 => 'Decillion',
143             10**36 => 'Undecillion',
144             10**39 => 'Duodecillion',
145             10**42 => 'Tredecillion',
146             10**45 => 'Quattuordecillion',
147             10**48 => 'Quindecillion',
148             10**51 => 'Sexdecillion',
149             10**54 => 'Septendecillion',
150             10**57 => 'Octodecillion',
151             10**60 => 'Novemdecillion',
152             10**63 => 'Vigintillion'
153             },
154             'British' => {
155             0 => 'Zero',
156             1 => 'One',
157             2 => 'Two',
158             3 => 'Three',
159             4 => 'Four',
160             5 => 'Five',
161             6 => 'Six',
162             7 => 'Seven',
163             8 => 'Eight',
164             9 => 'Nine',
165             10 => 'Ten',
166             11 => 'Eleven',
167             12 => 'Twelve',
168             13 => 'Thirteen',
169             14 => 'Fourteen',
170             15 => 'Fifteen',
171             16 => 'Sixteen',
172             17 => 'Seventeen',
173             18 => 'Eighteen',
174             19 => 'Nineteen',
175             20 => 'Twenty',
176             30 => 'Thirty',
177             40 => 'Fourty',
178             50 => 'Fifty',
179             60 => 'Sixty',
180             70 => 'Seventy',
181             80 => 'Eighty',
182             90 => 'Ninety',
183             10**2 => 'Hundred',
184             10**3 => 'Thousand',
185             10**6 => 'Million',
186             10**9 => 'Milliard',
187             10**12 => 'Billion',
188             10**15 => 'Billiard',
189             10**18 => 'Trillion',
190             10**21 => 'Trilliard',
191             10**24 => 'Quadrillion',
192             10**27 => 'Quadrilliard',
193             10**30 => 'Quintillion',
194             10**33 => 'Quintilliard',
195             10**36 => 'Sextillion',
196             10**39 => 'Sextilliard',
197             10**42 => 'Septillion',
198             10**45 => 'Septilliard',
199             10**48 => 'Octillion',
200             10**51 => 'Octilliard',
201             10**54 => 'Nonillian',
202             10**57 => 'Nonilliard',
203             10**60 => 'Decillion',
204             10**63 => 'Decilliard'
205             }
206             );
207             }
208              
209             # }}}
210              
211             # Exporter Routines
212             # {{{ import
213              
214             sub import {
215 2     2   710 my $module = shift;
216 2   100     15 my $tag = shift // 'American';
217 2 50 33     14 if (($tag eq 'American') || ($tag eq 'British')) {
218 2         4 $MODE = $tag;
219             }
220             else {
221 0 0       0 croak "Error: $module does not support tag: '$tag'.\n"
222             if ($tag);
223             }
224              
225 2         77 return;
226             }
227              
228             # }}}
229              
230             # Math Routines
231             # {{{ pow10Block
232              
233             sub pow10Block {
234 6     6 1 10 my ($number) = @_;
235 6 100       18 if ($number) {
236 5         142 return (int(pow10($number) / 3) * 3);
237             }
238             else {
239 1         36 return 0;
240             }
241             }
242              
243             # }}}
244             # {{{ pow10
245              
246             sub pow10 {
247 5     5 1 7 my ($number) = @_;
248 5         12 return (length $number) - 1;
249             }
250              
251             # }}}
252              
253             # Numeric String Parsing Routines
254             # {{{ string_to_number
255              
256             sub string_to_number {
257 2   50 2 1 7 my $numberString = shift // return '';
258              
259             # Strip out delimiters
260 2         57 $numberString =~ s/\Q$INPUT_GROUP_DELIMITER{$MODE}\E//g;
261              
262 2         49 my $sign = $SIGN_POSITIVE;
263 2 50       6 if ($numberString =~ /^-/) {
264 0         0 $numberString =~ s/^-//;
265 0         0 $sign = $SIGN_NEGATIVE;
266             }
267              
268 2 50       50 if (length($numberString)>1) { ### VSM 0.02 - Solve zero case
269 2         129 $numberString =~ s/^0+//g;
270             }
271              
272 2         46 my $number = '';
273 2         4 my $decimal = '';
274 2 50       30 if ($numberString =~ /(^.+)\Q$INPUT_DECIMAL_DELIMITER{$MODE}\E(.+$)/) {
275 0         0 ($number, $decimal) = ($1, $2);
276             }
277             else {
278 2         46 $number = $numberString;
279             }
280              
281 2 50       7 if ($number =~ /\D/) {
282 0         0 return ();
283             }
284 2 50 33     54 if ($decimal && ($decimal =~ /\D/)) {
285 0         0 return ();
286             }
287              
288 2         7 return ($number, $decimal, $sign);
289             }
290              
291             # }}}
292             # {{{ parse_number
293              
294             sub parse_number {
295 2     2 1 4 my ($number) = @_;
296              
297 2 50       8 if (! defined $number) { # VSM 0.02 - Number zero is not a valid condition
298 0         0 return { '0' => $NUMBER_NAMES{$MODE}{0} };
299             }
300              
301 2         33 my %names;
302 2         8 my $powerOfTen = pow10Block($number);
303 2         54 while ($powerOfTen > 0) {
304 4         64 my $factor = int($number / 10**$powerOfTen);
305 4         4179 my $component = $factor * 10**$powerOfTen;
306 4         613 my $magnitude = $NUMBER_NAMES{$MODE}{10**$powerOfTen};
307 4         13 my $factorName = &parse_number_low($factor);
308              
309 4         13 $names{$component}{'factor'} = $factorName;
310 4         96 $names{$component}{'magnitude'} = $magnitude;
311              
312 4         102 $number -= $component;
313 4         449 $powerOfTen = pow10Block($number);
314             }
315              
316 2 50       32 if (defined $number) { # VSM 0.02 - Number zero is not a valid condition
317 2         14 $names{'1'}{'factor'} = &parse_number_low($number);
318 2         6 $names{'1'}{'magnitude'} = '';
319             }
320              
321 2         11 return \%names;
322             }
323              
324             # }}}
325             # {{{ parse_number_low
326              
327             sub parse_number_low {
328 6     6 1 9 my ($number) = @_;
329 6         9 my @names;
330              
331 6 100       17 if ($number >= 100) {
332 3         244 my $hundreds = int($number / 10**2);
333 3         2480 push @names, [ $NUMBER_NAMES{$MODE}{$hundreds}, $NUMBER_NAMES{$MODE}{10**2} ];
334 3         77 $number -= $hundreds * 10**2;
335             }
336              
337 6 100       1245 if ($number >= 20) {
338 3         263 my $tens = int($number / 10**1) * 10**1;
339 3         3146 my $ones = $number - $tens;
340              
341 3 100       451 if ($ones) {
342 2         117 push @names, [ $NUMBER_NAMES{$MODE}{$tens} , $NUMBER_NAMES{$MODE}{$ones} ];
343             }
344             else {
345 1         30 push @names, [ $NUMBER_NAMES{$MODE}{$tens} ];
346             }
347             }
348             else {
349 3         266 push @names, [ $NUMBER_NAMES{$MODE}{$number} ];
350             }
351              
352 6         193 return \@names;
353             }
354              
355             # }}}
356              
357              
358             # Class Methods
359             # {{{ new
360              
361             sub new {
362 4     4 1 1497 my ($class, @initializer) = @_;
363              
364 4 50 33     26 if (! defined $class || ! $class) {
365 0         0 return ();
366             }
367              
368 4         8 my $self = {};
369 4         14 bless $self, $class;
370              
371 4 50       11 if (@initializer) {
372 0         0 $self->parse(@initializer);
373             }
374              
375 4         13 return $self;
376             }
377              
378             # }}}
379             # {{{ do_get_string
380              
381             sub do_get_string {
382 2     2 1 5 my ($self, $block) = @_;
383              
384 2 50 33     11 if (! defined $self || ! $self) {
385 0         0 return '';
386             }
387              
388 2 50 33     11 if (! defined $block || ! $block) {
389 0         0 return '';
390             }
391              
392 2         2 my @blockStrings;
393 2         6 my $number = $self->{'string_data'}{$block};
394 2         4 for my $component( sort {$b <=> $a } keys %{$number} ) {
  5         13  
  2         14  
395 6         26 my $magnitude = $$number{$component}{'magnitude'};
396 6         12 my $factor = $$number{$component}{'factor'};
397              
398 6         6 my @strings;
399 6         6 map { push @strings, join($OUTPUT_NUMBER_DELIMITER{$MODE}, @{$_}) } @{$factor};
  9         15  
  9         25  
  6         10  
400              
401 6         16 my $string = join($OUTPUT_GROUP_DELIMITER{$MODE}, @strings) . ' ' . $magnitude;
402 6         20 push @blockStrings, $string;
403             }
404              
405 2         9 my $blockString = join($OUTPUT_BLOCK_DELIMITER{$MODE}, @blockStrings);
406 2         11 $blockString =~ s{(?<=.),?\s?Zero}{}xmsg;
407              
408 2         7 return $blockString;
409             }
410              
411             # }}}
412             # {{{ parse
413              
414             sub parse {
415 4     4 1 114 my ($self, $numberString) = @_;
416              
417 4 50 66     190 if ( $numberString && $numberString =~ m{\A\d+\.?\d*?e\+\d+\z}xms ) {
418 0         0 croak q{You shouldn't use scientific notation};
419             }
420              
421 4 100 66     264 croak 'You should specify a number from interval [0, 10^66)'
      66        
      100        
422             if !defined $numberString
423             || $numberString !~ m{\A\d+\z}xms
424             || $numberString < 0
425             || $numberString >= 10 ** 66;
426              
427 2 50 33     1280 if (! defined $self || ! $self) {
428 0         0 return $FALSE;
429             }
430              
431 2         8 my ($number, $decimal, $sign) = &string_to_number($numberString);
432              
433 2         8 $self->{'numeric_data'}{'number'} = $number;
434 2         6 $self->{'numeric_data'}{'decimal'} = $decimal;
435 2         5 $self->{'numeric_data'}{'sign'} = $sign;
436              
437 2 50 33     24 if (defined $number && length($number)>0) { # VSM 0.02 - Number zero is not a valid condition
438 2         52 $self->{'string_data'}{'number'} = &parse_number($number);
439 2         10 $self->{'string_data'}{'sign'} = $SIGN_NAMES{$MODE}{$sign};
440             }
441              
442 2 50 33     16 if (defined $decimal && $decimal) {
443 0         0 $self->{'string_data'}{'decimal'} = &parse_number($decimal);
444             }
445              
446 2         11 return $TRUE;
447             }
448              
449             # }}}
450             # {{{ get_string
451              
452             sub get_string {
453 2     2 1 11 my ($self) = @_;
454              
455 2 50 33     12 if (! defined $self || ! $self) {
456 0         0 return '';
457             }
458              
459 2         3 my @strings;
460 2         8 push @strings, $self->do_get_string('number');
461              
462 2 50       8 if ($self->{'string_data'}{'decimal'}) {
463 0         0 push @strings, $self->do_get_string('decimal');
464             }
465              
466 2         6 my $string = join($OUTPUT_DECIMAL_DELIMITER{$MODE}, @strings);
467 2 50       10 if ($self->{'string_data'}{'sign'}) {
468 0         0 $string = $self->{'string_data'}{'sign'} . " $string";
469             }
470              
471 2         21 $string =~ s/\s+$//;
472 2         16 return $string;
473             }
474              
475             # }}}
476              
477             1;
478              
479             __END__