File Coverage

blib/lib/Lingua/ID/Nums2Words.pm
Criterion Covered Total %
statement 67 67 100.0
branch 28 32 87.5
condition 51 62 82.2
subroutine 12 12 100.0
pod 2 2 100.0
total 160 175 91.4


line stmt bran cond sub pod time code
1             package Lingua::ID::Nums2Words;
2              
3             our $DATE = '2015-09-03'; # DATE
4             our $VERSION = '0.04'; # VERSION
5              
6 1     1   33840 use 5.010001;
  1         4  
7 1     1   6 use strict;
  1         1  
  1         22  
8 1     1   5 use warnings;
  1         2  
  1         119  
9              
10             require Exporter;
11             our @ISA = qw(Exporter);
12             our @EXPORT_OK = qw(nums2words nums2words_simple);
13              
14             our %SPEC;
15              
16 1         1292 use vars qw(
17             $Dec_char
18             $Neg_word
19             $Dec_word
20             $Exp_word
21             $Zero_word
22             %Digit_words
23             %Mult_words
24 1     1   7 );
  1         3  
25              
26             $Dec_char = ".";
27             $Neg_word = "negatif";
28             $Dec_word = "koma";
29             $Exp_word = "dikali sepuluh pangkat";
30             $Zero_word = "nol";
31              
32             %Digit_words = (
33             0 => $Zero_word,
34             1 => 'satu',
35             2 => 'dua',
36             3 => 'tiga',
37             4 => 'empat',
38             5 => 'lima',
39             6 => 'enam',
40             7 => 'tujuh',
41             8 => 'delapan',
42             9 => 'sembilan'
43             );
44              
45             %Mult_words = (
46             0 => '',
47             1 => 'ribu',
48             2 => 'juta',
49             3 => 'milyar',
50             4 => 'triliun'
51             );
52              
53             $SPEC{nums2words} = {
54             v => 1.1,
55             summary => 'Convert number to Indonesian verbage',
56             description => <<'_',
57              
58             This is akin to converting 123 to "a hundred and twenty three" in English.
59             Currently can handle real numbers in normal and scientific form in the order of
60             hundreds of trillions. It also preserves formatting in the number string (e.g,
61             given "1.00" `nums2words` will pronounce the zeros.
62              
63             _
64             args => {
65             num => {
66             schema => 'str*',
67             summary => 'The number to convert',
68             req => 1,
69             pos => 0,
70             },
71             },
72             args_as => 'array',
73             result_naked => 1,
74             };
75 29     29 1 9218 sub nums2words($) { _join_it(_handle_scinotation(@_)) }
76              
77             $SPEC{nums2words_simple} = {
78             v => 1.1,
79             summary => 'Like nums2words but only pronounce the digits',
80             description => <<'_',
81              
82             This is akin to converting 123 to "one two three" in English.
83              
84             _
85             args => {
86             num => {
87             schema => 'str*',
88             summary => 'The number to convert',
89             req => 1,
90             pos => 0,
91             },
92             },
93             args_as => 'array',
94             result_naked => 1,
95             };
96 5     5 1 1569 sub nums2words_simple($) { _join_it(_handle_dec(@_)) }
97              
98             sub _handle_scinotation($) {
99 29     29   40 my $num = shift;
100 29         26 my @words;
101              
102 29 100 66     133 $num =~ /^(.+)[Ee](.+)$/ and
103             @words = (_handle_neg_dec($1), $Exp_word, _handle_neg_dec($2)) or
104             @words = _handle_neg_dec($num);
105              
106 29         97 @words;
107             }
108              
109             sub _handle_neg_dec($) {
110 33     33   45 my $num = shift;
111 33         34 my $is_neg;
112 33         42 my @words = ();
113              
114 33 100       71 $num < 0 and $is_neg++;
115 33         125 $num =~ s/^[\s\t]*[+-]*(.*)/$1/;
116              
117             $num =~ /^(.+)\Q$Dec_char\E(.+)$/o and
118             @words = (_handle_int($1), $Dec_word, _handle_dec($2)) or
119              
120             $num =~ /^\Q$Dec_char\E(.+)$/o and
121 33 50 50     267 @words = ($Digit_words{0}, $Dec_word, _handle_dec($1)) or
      66        
      33        
      66        
122              
123             $num =~ /^(.+)(?:\Q$Dec_char\E)?$/o and
124             @words = _handle_int($1);
125              
126 33 100       75 $is_neg and
127             unshift @words, $Neg_word;
128              
129 33         107 @words;
130             }
131              
132             # handle digits before decimal
133             sub _handle_int($) {
134 33     33   54 my $num = shift;
135 33         39 my @words = ();
136 33         34 my $order = 0;
137 33         24 my $t;
138              
139 33         120 while($num =~ /^(.*?)([\d\D*]{1,3})$/) {
140 57         96 $num = $1;
141 57         94 ($t = $2) =~ s/\D//g;
142 57 100       153 unshift @words, $Mult_words{$order} if $t > 0;
143 57         91 unshift @words, _handle_thousand($t, $order);
144 57         172 $order++;
145             }
146              
147 33 100       119 @words = ($Zero_word) if not join('',@words)=~/\S/;
148 33         105 @words;
149             }
150              
151             sub _handle_thousand($$) {
152 57     57   67 my $num = shift;
153 57         58 my $order = shift;
154 57         69 my @words = ();
155              
156 57         68 my $n1 = $num % 10;
157 57         99 my $n2 = ($num % 100 - $n1) / 10;
158 57         82 my $n3 = ($num - $n2*10 - $n1) / 100;
159              
160             $n3 == 0 && $n2 == 0 && $n1 > 0 and (
161             $n1 == 1 && $order == 1 and @words = ("se") or
162 57 100 100     323 @words = ($Digit_words{$n1}) );
      66        
      100        
      100        
      100        
163              
164             $n3 == 1 and @words = ("seratus") or
165 57 100 100     167 $n3 > 1 and @words = ($Digit_words{$n3}, "ratus");
      66        
166              
167             $n2 == 1 and (
168             $n1 == 0 and push(@words, "sepuluh") or
169             $n1 == 1 and push(@words, "sebelas") or
170 57 100 66     133 push(@words, $Digit_words{$n1}, "belas")
      66        
      66        
      100        
171             );
172              
173 57 100       86 $n2 > 1 and do {
174 7         13 push @words, $Digit_words{$n2}, "puluh";
175 7 50       19 push @words, $Digit_words{$n1} if $n1 > 0;
176             };
177              
178             $n3 > 0 && $n2 == 0 && $n1 > 0 and
179 57 100 100     142 push @words, $Digit_words{$n1};
      100        
180              
181 57 100 100     410 $n3 != 0 || $n2 != 0 || $n1 != 0 and
      100        
182             @words;
183             }
184              
185             # handle digits after decimal
186             sub _handle_dec($) {
187 13     13   20 my $num = shift;
188 13         17 my @words = ();
189 13         13 my $i;
190             my $t;
191              
192 13         36 for( $i=0; $i<=length($num)-1; $i++ ) {
193 30         41 $t = substr($num, $i, 1);
194             exists $Digit_words{$t} and
195 30 50       113 push @words, $Digit_words{$t};
196             }
197              
198 13 50       45 @words = ($Zero_word) if not join('',@words)=~/\S/;
199 13         54 @words;
200             }
201              
202             # join array of words, also join (se, ratus) -> seratus, etc.
203             sub _join_it(@) {
204 34     34   43 my $words = '';
205 34         30 my $w;
206              
207 34         73 while(defined( $w = shift )) {
208 174         198 $words .= $w;
209 174 100 100     943 $words .= ' ' unless not length $w or $w eq 'se' or not @_;
      100        
210             }
211 34         61 $words =~ s/^\s+//;
212 34         100 $words =~ s/\s+$//;
213 34         161 $words;
214             }
215              
216             1;
217             # ABSTRACT: Convert number to Indonesian verbage
218              
219             __END__