File Coverage

blib/lib/Lingua/TR/Numbers.pm
Criterion Covered Total %
statement 148 158 93.6
branch 62 82 75.6
condition 19 33 57.5
subroutine 26 28 92.8
pod 2 2 100.0
total 257 303 84.8


line stmt bran cond sub pod time code
1             package Lingua::TR::Numbers;
2 2     2   30518 use 5.006;
  2         6  
3 2     2   10 use utf8;
  2         2  
  2         15  
4 2     2   48 use strict;
  2         7  
  2         52  
5 2     2   8 use warnings;
  2         3  
  2         75  
6 2     2   12 use subs qw( _log );
  2         3  
  2         11  
7              
8             our $VERSION = '0.32';
9              
10 2         243 use constant RE_E2TR => qr{
11             \A
12             (
13             [-+]? # leading sign
14             (?:
15             [\d,]+ | [\d,]*\.\d+ # number
16             )
17             )
18             [eE]
19             (-?\d+) # mantissa, has to be an integer
20             \z
21 2     2   406 }xms;
  2         4  
22 2     2   10 use constant RE_EMPTY => qr//xms;
  2         4  
  2         110  
23 2     2   10 use constant EMPTY_STRING => q{};
  2         2  
  2         98  
24 2     2   9 use constant SPACE => q{ };
  2         2  
  2         135  
25 2     2   14 use constant DIGITS => 0..9;
  2         2  
  2         138  
26 2     2   8 use constant TENS => map { 10 * $_ } 1..9;
  2         0  
  2         4  
  18         106  
27 2     2   6 use constant LAST_ELEMENT => -1;
  2         3  
  2         65  
28 2     2   6 use constant PREV_ELEMENT => -2;
  2         5  
  2         74  
29 2     2   7 use constant CHUNK_MAX => 100;
  2         2  
  2         69  
30 2     2   10 use base qw( Exporter );
  2         2  
  2         182  
31 2     2   8 use Carp qw( croak );
  2         1  
  2         166  
32              
33 2 50   2   2579 BEGIN { *DEBUG = sub () {0} if ! defined &DEBUG } # setup a DEBUG constant
34              
35             our @EXPORT_OK = qw( num2tr num2tr_ordinal );
36             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
37              
38             my($RE_VOWEL, %D, %MULT, %CARD2ORD, %CARD2ORDTR);
39              
40             POPULATE: {
41             @D{ DIGITS() } = qw| sıfır bir iki üç dört beş altı yedi sekiz dokuz |;
42             @D{ TENS() } = qw| on yirmi otuz kırk elli altmış yetmiş seksen doksan |;
43              
44             @CARD2ORD{ qw| bir iki üç dört beş altı yedi sekiz dokuz |}
45             = qw| birinci ikinci üçüncü dördüncü beşinci altıncı yedinci sekizinci dokuzuncu |;
46              
47             @CARD2ORDTR{ qw| a e ı i u ü o ö |}
48             = qw| ncı nci ncı nci ncu ncü ncu ncü |;
49              
50             $RE_VOWEL = join EMPTY_STRING, keys %CARD2ORDTR;
51             $RE_VOWEL = qr{([$RE_VOWEL])}xms;
52              
53             my @large = qw|
54             bin milyon milyar trilyon katrilyon
55             kentilyon seksilyon septilyon oktilyon nobilyon
56             desilyon
57             |;
58             my $c = 0;
59             $MULT{ $c++ } = $_ for EMPTY_STRING, @large;
60             }
61              
62             sub num2tr_ordinal {
63             # Cardinals are [bir iki üç ...]
64             # Ordinals are [birinci ikinci üçüncü ...]
65 1     1 1 2 my $x = shift;
66              
67 1 50 33     7 return unless defined $x and length $x;
68              
69 1         3 $x = num2tr( $x );
70 1 50       4 return $x if ! $x;
71              
72 1         1 my($ok, $end, $step);
73 1 50       9 if ( $x =~ s/(\w+)\z//xms ) {
74 1         2 $end = $1;
75 1         4 my @l = split RE_EMPTY, $end;
76 1         2 $step = 1;
77              
78 1         2 foreach my $l ( reverse @l ) {
79 2 50       4 next if not $l;
80 2 100       19 if ( $l =~ $RE_VOWEL ) {
81 1         13 $ok = $1;
82 1         2 last;
83             }
84 1         1 $step++;
85             }
86             }
87             else {
88 0         0 return $x . q{.};
89             }
90              
91 1 50       3 if ( ! $ok ) {
92             #die "Can not happen: '$end'";
93 0         0 return;
94             }
95              
96             $end = $CARD2ORD{$end} || sub {
97 0     0   0 my $val = $CARD2ORDTR{$ok};
98 0         0 return $end . $val if $step == 1;
99 0         0 my $letter = (split RE_EMPTY, $val)[LAST_ELEMENT];
100 0         0 return $end.$letter.$val;
101 1   33     5 }->();
102              
103 1         4 return "$x$end";
104             }
105              
106             sub num2tr {
107 78     78 1 888 my $x = shift;
108 78 100 100     345 return unless defined $x and length $x;
109              
110 76 50       136 return 'sayı-değil' if $x eq 'NaN';
111 76 50       138 return 'eksi sonsuz' if $x =~ m/ \A \+ inf(?:inity)? \z /xmsi;
112 76 50       101 return 'artı sonsuz' if $x =~ m/ \A \- inf(?:inity)? \z /xmsi;
113 76 50       143 return 'sonsuz' if $x =~ m/ \A inf(?:inity)? \z /xmsi;
114 76 100       191 return $D{$x} if exists $D{$x}; # the most common cases
115              
116             # Make sure it's not in scientific notation:
117 55 100       39 { my $e = _e2tr($x); return $e if defined $e; }
  55         71  
  55         99  
118              
119 52         42 my $orig = $x;
120              
121 52         54 $x =~ s/,//xmsg; # nix any commas
122              
123 52         38 my $sign;
124 52 100       109 if ( $x =~ s/\A([-+])//xms ) {
125 11         19 $sign = $1;
126             }
127              
128 52         50 my($int, $fract);
129 52 100       151 if( $x =~ m/ \A \d+ \z/xms ) { $int = $x }
  38 100       38  
    100          
130 7         11 elsif( $x =~ m/ \A (\d+)[.](\d+) \z/xms ) { $int = $1; $fract = $2 }
  7         10  
131 3         7 elsif( $x =~ m/ \A [.](\d+) \z/xms ) { $fract = $1 }
132             else {
133 4         2 _log "Not a number: '$orig'\n" if DEBUG;
134 4         16 return;
135             }
136              
137             _log(
138             sprintf " Working on Sign[%s] Int2tr[%s] Fract[%s] < '%s'\n",
139 48         55 map { defined($_) ? $_ : 'nil' } $sign, $int, $fract, $orig
140             ) if DEBUG;
141              
142 48 100       63 return join SPACE, grep { defined $_ && length $_ }
  103         486  
143             _sign2tr( $sign ),
144             _int2tr( $int ),
145             _fract2tr( $fract ),
146             ;
147             }
148              
149             sub _sign2tr {
150 48     48   41 my $x = shift;
151 48 50 66     167 return ! defined $x || ! length $x ? undef
    100          
    100          
152             : $x eq q{-} ? 'eksi'
153             : $x eq q{+} ? 'artı'
154             : "WHAT_IS_$x"
155             ;
156             }
157              
158             sub _fract2tr { # "1234" => "point one two three four"
159 48     48   38 my $x = shift;
160 48 100 66     127 return unless defined $x and length $x;
161             return join SPACE, 'nokta',
162 10         26 map { $D{$_} }
  22         46  
163             split RE_EMPTY, $x;
164             }
165              
166             # The real work:
167              
168             sub _int2tr {
169 68     68   59 my $x = shift;
170 68 50 66     339 return unless defined $x and length $x and $x =~ m/\A\d+\z/xms;
      66        
171 65 100       133 return $D{$x} if defined $D{$x}; # most common/irreg cases
172              
173 47 100       100 if( $x =~ m/\A(.)(.)\z/xms ) {
    100          
174 31         162 return $D{$1 . '0'} . SPACE . $D{$2};
175             # like forty - two
176             # note that neither bit can be zero at this point
177             }
178             elsif ( $x =~ m/\A(.)(..)\z/xms ) {
179 9 100       31 my $tmp = $1 == 1 ? EMPTY_STRING : $D{$1} . SPACE;
180 9         21 my($h, $rest) = ($tmp.'yüz', $2);
181 9 100       31 return $h if $rest eq '00';
182 8         22 return "$h " . _int2tr(0 + $rest);
183             }
184             else {
185 7         12 return _bigint2tr($x);
186             }
187             }
188              
189             sub _bigint2tr {
190 7     7   8 my $x = shift;
191 7 50 33     43 return unless defined $x and length $x and $x =~ m/\A\d+\z/xms;
      33        
192 7         5 my @chunks; # each: [ string, exponent ]
193             {
194 7         8 my $groupnum = 0;
  7         6  
195 7         4 my $num;
196 7         25 while ( $x =~ s/(\d{1,3})\z//xms ) { # pull at most three digits from the end
197 16         25 $num = $1 + 0;
198 16 100       33 unshift @chunks, [ $num, $groupnum ] if $num;
199 16         36 ++$groupnum;
200             }
201 7 50       44 return $D{'0'} unless @chunks; # rare but possible
202             }
203              
204 7         5 my $and;
205             # junk
206 7 100 100     28 $and = EMPTY_STRING if $chunks[LAST_ELEMENT][1] == 0 and $chunks[LAST_ELEMENT][0] < CHUNK_MAX;
207             # The special 'and' that shows up in like "one thousand and eight"
208             # and "two billion and fifteen", but not "one thousand [*and] five hundred"
209             # or "one million, [*and] nine"
210              
211 7         16 _chunks2tr( \@chunks );
212              
213 7 50 33     14 $chunks[PREV_ELEMENT] .= SPACE if $and and @chunks > 1;
214 7 100       22 return "$chunks[0] $chunks[1]" if @chunks == 2;
215             # Avoid having a comma if just two units
216 2         5 return join q{, }, @chunks;
217             }
218              
219             sub _chunks2tr {
220 7     7   8 my $chunks = shift;
221 7 50       5 return if ! @{ $chunks };
  7         13  
222 7         4 my @out;
223 7         8 foreach my $c ( @{ $chunks } ) {
  7         11  
224 12 50       26 push @out, $c = _groupify( _int2tr( $c->[0] ), $c->[1] ,$c->[0]) if $c->[0];
225             }
226 7         7 @{ $chunks } = @out;
  7         13  
227 7         7 return;
228             }
229              
230             sub _groupify {
231             # turn ("seventeen", 3) => "seventeen billion"
232 12     12   21 my($basic, $multnum, $raw) = @_;
233 12 100       23 return $basic unless $multnum; # the first group is unitless
234 7         6 _log " Groupifying $basic x $multnum mults\n" if DEBUG > 2;
235 7 50       36 return "$basic $MULT{$multnum}" if $MULT{$multnum};
236             # Otherwise it must be huuuuuge, so fake it with scientific notation
237 0         0 return $basic . ' çarpı on üzeri ' . num2tr( $raw * 3 );
238             }
239              
240             # Because I can never remember this:
241             #
242             # 3.1E8
243             # ^^^ is called the "mantissa"
244             # ^ is called the "exponent"
245             # (the implicit "10" is the "base" a/k/a "radix")
246              
247             sub _e2tr {
248 55     55   51 my $x = shift;
249 55 100       150 if ( $x =~ RE_E2TR ) {
250 3         7 my($m, $e) = ($1, $2);
251 3         1 _log " Scientific notation: [$x] => $m E $e\n" if DEBUG;
252 3         7 $e += 0;
253 3         6 return num2tr($m) . ' çarpı on üzeri ' . num2tr($e);
254             }
255             else {
256 52         37 _log " Okay, $x isn't in exponential notation\n" if DEBUG;
257 52         57 return;
258             }
259             }
260              
261             sub _log {
262 0     0     my @args = @_;
263 0 0         print @args or croak "Unable to print to STDOUT: $!";
264 0           return;
265             }
266              
267             #==========================================================================
268              
269             1;
270              
271             __END__