File Coverage

blib/lib/Lingua/IT/Numbers.pm
Criterion Covered Total %
statement 134 153 87.5
branch 50 70 71.4
condition 9 12 75.0
subroutine 20 23 86.9
pod 5 13 38.4
total 218 271 80.4


line stmt bran cond sub pod time code
1             package Lingua::IT::Numbers;
2 5     5   71801 use Regexp::Common qw(number);
  5         51486  
  5         29  
3              
4 5     5   24780 use strict;
  5         12  
  5         267  
5 5     5   27 use warnings;
  5         15  
  5         724  
6              
7             use overload (
8 5         63 '""' => \&get_string,
9             '0+' => \&get_number,
10             '=' => \&clone,
11             '+' => \&add,
12             '-' => \&minus,
13             '*' => \&mult,
14             '/' => \&div,
15 5     5   30 fallback => 1);
  5         10  
16              
17              
18 5     5   972 use Carp qw(carp);
  5         8  
  5         406  
19 5     5   27 use Exporter;
  5         14  
  5         350  
20 5     5   33 use vars qw( $VERSION @ISA @EXPORT_OK @EXPORT %EXPORT_TAGS);
  5         17  
  5         499  
21 5         7387 use vars qw(
22             %NUMBER_NAMES
23             @PART_NAMES
24             @UNITS_NAMES
25             @FRACT_NAMES
26             %OUTPUT_DECIMAL_DELIMITERS
27             %SIGN_NAMES
28             %DEFAULT_OPTS
29 5     5   28 );
  5         65  
30              
31             require Exporter;
32              
33             @ISA = qw(Exporter);
34             $VERSION = '0.06';
35              
36             @EXPORT = ();
37              
38             @EXPORT_OK = qw(number_to_it);
39              
40             %EXPORT_TAGS = (
41             all => [@EXPORT_OK],
42             default => [@EXPORT],
43             );
44              
45             %SIGN_NAMES = ('-' => 'meno',
46             '+' => '');
47              
48             %OUTPUT_DECIMAL_DELIMITERS = (normal => 'virgola',
49             fract => 'e');
50              
51             %NUMBER_NAMES = (
52             0 => 'zero',
53             1 => 'un',
54             2 => 'due',
55             3 => 'tre',
56             4 => 'quattro',
57             5 => 'cinque',
58             6 => 'sei',
59             7 => 'sette',
60             8 => 'otto',
61             9 => 'nove',
62             10 => 'dieci',
63             11 => 'undici',
64             12 => 'dodici',
65             13 => 'tredici',
66             14 => 'quattordici',
67             15 => 'quindici',
68             16 => 'sedici',
69             17 => 'diciassette',
70             18 => 'diciotto',
71             19 => 'diciannove',
72             20 => 'venti',
73             30 => 'trenta',
74             40 => 'quaranta',
75             50 => 'cinquanta',
76             60 => 'sessanta',
77             70 => 'settanta',
78             80 => 'ottanta',
79             90 => 'novanta',
80             100 => 'cento');
81              
82             @PART_NAMES = qw(_ mila milioni miliardi);
83             @UNITS_NAMES = qw(_ mille milione miliardo);
84             @FRACT_NAMES = qw(decim centesim millesim decimillesim centomillesim
85             milionesim decimilionesim centomilionesim
86             miliardesim);
87              
88             %DEFAULT_OPTS = (
89             decimal => 0,
90             decmode => 'normal',
91             name => "",
92             );
93              
94             sub number_to_it {
95 28     28 1 4017 my ($number,$opts) = @_;
96 28 100       86 $opts = {} unless defined $opts;
97 28         158 $opts = {%DEFAULT_OPTS,%$opts};
98 28         87 my $parsed = parse_num_string($number);
99 28         47 my @parts = ();
100 28         74 push @parts,$SIGN_NAMES{$parsed->{sign}};
101 28         77 my $intpart = convert_to_string($parsed->{intpart});
102 28         53 my $one = $NUMBER_NAMES{1};
103 28         5248 $intpart =~ s/($one)$/$1o/;
104 28         51 push @parts,$intpart;
105 28 50       93 if ($opts->{name}) {
106 0 0       0 my $name = ! ref($opts->{name}) ? $opts->{name} :
    0          
107             ($intpart eq $NUMBER_NAMES{1} ? $opts->{name}[0] : $opts->{name}[1]);
108            
109 0         0 push @parts,$name;
110             }
111            
112 28 100 66     130 if ($parsed->{fracpart} || $opts->{decimal}) {
113 12         30 push @parts,$OUTPUT_DECIMAL_DELIMITERS{$opts->{decmode}};
114 12 100       52 if ($opts->{decimal}) {
115 2 50       10 if (length($parsed->{fracpart}) < $opts->{decimal}) {
116 2         9 $parsed->{fracpart} .= "0" x ($opts->{decimal} -
117             length($parsed->{fracpart}));
118             }
119             }
120 12         36 my $fractpart = convert_to_string($parsed->{fracpart});
121 12 100       47 if ($opts->{decmode} eq 'fract') {
122 1 50       5 if ($fractpart eq $NUMBER_NAMES{1}) {
123 0         0 push @parts,$fractpart,$FRACT_NAMES[length($parsed->{fracpart}) - 1] . "o";
124             }
125             else {
126 1         5 push @parts,$fractpart,$FRACT_NAMES[length($parsed->{fracpart}) - 1] . "i";
127             }
128             }
129             else {
130 11         22 my $one = $NUMBER_NAMES{1};
131 11         72 $fractpart =~ s/($one)$/$1o/;
132 11         38 push @parts,$fractpart;
133             }
134             }
135 28         66 my $result = join(" ",@parts);
136 28         86 $result =~ s/^\s*//;
137 28         301 return $result;
138             }
139              
140             sub convert_short {
141 5     5   6796 use integer;
  5         55  
  5         27  
142 48     48 0 71 my $num = shift; # 1 < num < 1000
143              
144 48         67 my $hundreds = $num / 100;
145 48         61 my $tens = $num % 100;
146 48         69 my @parts = ();
147 48 100       128 if ($hundreds == 1) {
    100          
148 11         119 push @parts,$NUMBER_NAMES{100};
149             }
150             elsif ($hundreds > 1) {
151 14         49 push @parts,$NUMBER_NAMES{$hundreds},$NUMBER_NAMES{100};
152             }
153 48 100       125 if ($tens == 0) {
    100          
154             #nothing
155             ;
156             }
157             elsif ($tens <= 20) {
158 14         33 push @parts,$NUMBER_NAMES{$tens};
159             }
160             else {
161 32         52 my $units = $tens % 10;
162 32         39 $tens = $tens - $units;
163 32         66 my $tenstr = $NUMBER_NAMES{$tens};
164 32 100 100     186 $tenstr =~ s/.$// if ($units == 1) or ($units == 8);
165 32         52 push @parts,$tenstr;
166 32 50       89 if ($units >= 1) {
167 32         80 push @parts,$NUMBER_NAMES{$units};
168             }
169             }
170 48         173 return join("",@parts);
171             }
172              
173              
174             sub convert_to_string {
175 5     5   1276 use integer;
  5         18  
  5         29  
176 40     40 0 96 my $number = shift; #$number >= 0 and integer
177 40 50       150 return $NUMBER_NAMES{0} unless $number =~ m/[1-9]/;
178 40 100       118 return $NUMBER_NAMES{1} if "$number" eq "1" ;
179 38 100       140 if (my $r = length($number) % 3) {
180 23         65 $number = "0" x (3 - $r) . $number;
181             }
182 38         166 my @blocks = ($number =~ m!(\d\d\d)!g);
183 38         61 @blocks = reverse @blocks;
184 38 50       91 if (@blocks > 4) {
185 0         0 carp "Numbers bigger than 1e10-1 not handled in version $VERSION";
186 0         0 return;
187             }
188 38         54 my @name_parts = ();
189 38         52 my $firstpart = "";
190 38 100       137 if ($blocks[0] == 1) {
    50          
191             #nb one of the following blocks is != 0, since the whole number
192             #is greater than one
193 2         4 $firstpart = $NUMBER_NAMES{1};
194             }
195             elsif ($blocks[0] > 1) {
196 36         77 $firstpart = convert_short($blocks[0]);
197             }
198 38 50 66     242 if ($#blocks >= 1 && $blocks[1] == 1) {
    100 66        
199 0         0 $firstpart = $UNITS_NAMES[1] . $firstpart;
200             }
201             elsif ($#blocks >= 1 && $blocks[1] > 1) {
202 8         21 $firstpart = convert_short($blocks[1]) . $PART_NAMES[1] . $firstpart;
203             }
204 38         64 push @name_parts,$firstpart;
205 38         98 foreach my $pos (2..$#blocks) {
206 4 50       14 next unless $blocks[$pos];
207 4         8 push @name_parts," ";
208 4 50       12 if ($blocks[$pos] == 1) {
209 0         0 push @name_parts,$NUMBER_NAMES{1} . " " . $UNITS_NAMES[$pos];
210             }
211             else {
212 4         10 my $part = convert_short($blocks[$pos]);
213 4         17 push @name_parts,$part. " " . $PART_NAMES[$pos];
214             }
215             }
216 38         83 my $tmp = join("",reverse(@name_parts));
217 38         118 $tmp =~ s/^\s*//;
218 38         330 $tmp =~ s/\s*$//;
219 38         77 $tmp =~ s!\s+! !g;
220 38         112 return $tmp;
221             }
222              
223              
224             sub parse_num_string {
225 28     28 0 44 my $string = shift;
226 28         49 my %parsed = ();
227 28 50       172 unless ($string =~
228             $RE{num}{real}{-keep}{-radix => qr/[,.]/}) {
229 0         0 carp("Invalid number format: '$string'");
230 0         0 return undef;
231             }
232 28 50       10578 if (defined $7) {
233 0         0 carp("Invalid number format: '$string'");
234 0         0 return undef;
235             }
236 28 100       341 @parsed{qw(sign intpart fracpart)}
    100          
237             = (($2 ? $2 : '+'),
238             $4,
239             defined $6 ? $6 : 0);
240 28         768 $parsed{intpart} =~ s/\.|\s+//g;
241 28         116 return \%parsed;
242             }
243              
244             #
245             # OO Methods
246             #
247             sub new {
248 15     15 1 12136 my $class = shift;
249 15         30 my $number = shift;
250 15         83 my %opts = (%DEFAULT_OPTS,@_);
251 15         103 bless { number => $number,
252             opts => \%opts}, $class;
253             }
254              
255              
256             sub get_string {
257 19     19 1 162 my $self = shift;
258 19         490 number_to_it($self->{number},$self->{opts});
259             }
260              
261             sub get_number {
262 0     0 1 0 my $self = shift;
263 0         0 return $self->{number}
264             }
265              
266             sub set_number {
267 0     0 1 0 my $self = shift;
268 0         0 $self->{number} = shift;
269 0         0 return $self;
270             }
271              
272             sub add {
273 2     2 0 11 my $self = shift;
274 2         4 my $num = shift;
275 2 100       10 $num = UNIVERSAL::isa($num,__PACKAGE__) ? $num->{number} : $num;
276 2         7 my $tmp = $self->{number} + $num;
277 2         13 return bless {number => $tmp,
278             opts => $self->{opts}},ref($self);
279             }
280              
281             sub mult {
282 1     1 0 3 my $self = shift;
283 1         3 my $num = shift;
284 1 50       9 $num = UNIVERSAL::isa($num,__PACKAGE__) ? $num->{number} : $num;
285 1         8 bless {number => $self->{number} * $num,
286             opts => $self->{opts}},ref($self);
287             }
288              
289             sub div {
290 1     1 0 3 my $self = shift;
291 1         2 my $num = shift;
292 1 50       8 $num = UNIVERSAL::isa($num,__PACKAGE__) ? $num->{number} : $num;
293 1         2 my $inverted = shift;
294 1 50       5 my $tmp =
295             ($inverted) ? $num / $self->{number} : $self->{number} / $num;
296 1         7 return bless {number => $tmp,
297             opts => $self->{opts}},ref($self);
298             }
299              
300             sub minus {
301 2     2 0 5 my $self = shift;
302 2         3 my $num = shift;
303 2 100       10 $num = UNIVERSAL::isa($num,__PACKAGE__) ? $num->{number} : $num;
304 2         4 my $inverted = shift;
305 2 50       8 my $tmp =
306             ($inverted) ? $num - $self->{number} : $self->{number} - $num;
307 2         13 return bless {number => $tmp,
308             opts => $self->{opts}},ref($self);
309             }
310              
311             sub clone {
312 0     0 0   my $self = shift;
313 0           my $class = ref($self);
314 0           bless {%$self},$class;
315             }
316             1;
317             __END__