File Coverage

blib/lib/Lingua/TR/Numbers.pm
Criterion Covered Total %
statement 150 161 93.1
branch 62 82 75.6
condition 22 38 57.8
subroutine 26 28 92.8
pod 2 2 100.0
total 262 311 84.2


line stmt bran cond sub pod time code
1             package Lingua::TR::Numbers;
2             $Lingua::TR::Numbers::VERSION = '0.35';
3 2     2   118702 use 5.010;
  2         36  
4 2     2   8 use utf8;
  2         3  
  2         11  
5 2     2   49 use strict;
  2         4  
  2         59  
6 2     2   9 use warnings;
  2         4  
  2         53  
7 2     2   8 use subs qw( _log );
  2         4  
  2         17  
8              
9 2         202 use constant RE_E2TR => qr{
10             \A
11             (
12             [-+]? # leading sign
13             (?:
14             [\d,]+ | [\d,]*\.\d+ # number
15             )
16             )
17             [eE]
18             (-?\d+) # mantissa, has to be an integer
19             \z
20 2     2   236 }xms;
  2         3  
21 2     2   12 use constant RE_EMPTY => qr//xms;
  2         4  
  2         116  
22 2     2   10 use constant EMPTY_STRING => q{};
  2         4  
  2         75  
23 2     2   9 use constant SPACE => q{ };
  2         3  
  2         112  
24 2     2   10 use constant DIGITS => 0..9;
  2         3  
  2         170  
25 2     2   11 use constant TENS => map { 10 * $_ } 1..9;
  2         3  
  2         4  
  18         128  
26 2     2   10 use constant LAST_ELEMENT => -1;
  2         2  
  2         83  
27 2     2   8 use constant PREV_ELEMENT => -2;
  2         4  
  2         74  
28 2     2   10 use constant CHUNK_MAX => 100;
  2         3  
  2         87  
29 2     2   849 use parent qw( Exporter );
  2         583  
  2         8  
30 2     2   129 use Carp qw( croak );
  2         5  
  2         151  
31              
32             BEGIN {
33 2   50 2   15 my $DEBUG = 0 + ( $ENV{DEBUG} || 0 );
34 0         0 *DEBUG = sub () { $DEBUG }
35 2         3359 }
36              
37             our @EXPORT_OK = qw( num2tr num2tr_ordinal );
38             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
39              
40             my($RE_VOWEL, %D, %MULT, %CARD2ORD, %CARD2ORDTR);
41              
42             POPULATE: {
43             @D{ DIGITS() } = qw| sıfır bir iki üç dört beş altı yedi sekiz dokuz |;
44             @D{ TENS() } = qw| on yirmi otuz kırk elli altmış yetmiş seksen doksan |;
45              
46             @CARD2ORD{ qw| bir iki üç dört beş altı yedi sekiz dokuz |}
47             = qw| birinci ikinci üçüncü dördüncü beşinci altıncı yedinci sekizinci dokuzuncu |;
48              
49             @CARD2ORDTR{ qw| a e ı i u ü o ö |}
50             = qw| ncı nci ncı nci ncu ncü ncu ncü |;
51              
52             $RE_VOWEL = join EMPTY_STRING, keys %CARD2ORDTR;
53             $RE_VOWEL = qr{([$RE_VOWEL])}xms;
54              
55             my @large = qw|
56             bin milyon milyar trilyon katrilyon
57             kentilyon seksilyon septilyon oktilyon nobilyon
58             desilyon
59             |;
60             my $c = 0;
61             $MULT{ $c++ } = $_ for EMPTY_STRING, @large;
62             }
63              
64             sub num2tr_ordinal {
65             # Cardinals are [bir iki üç ...]
66             # Ordinals are [birinci ikinci üçüncü ...]
67 1     1 1 1 my $x = shift;
68              
69 1 50 33     8 return unless defined $x and length $x;
70              
71 1         3 $x = num2tr( $x );
72 1 50       4 return $x if ! $x;
73              
74 1         2 my($ok, $end, $step);
75 1 50       8 if ( $x =~ s/(\w+)\z//xms ) {
76 1         3 $end = $1;
77 1         3 my @l = split RE_EMPTY, $end;
78 1         2 $step = 1;
79              
80 1         2 foreach my $l ( reverse @l ) {
81 2 50       6 next if not $l;
82 2 100       11 if ( $l =~ $RE_VOWEL ) {
83 1         2 $ok = $1;
84 1         3 last;
85             }
86 1         2 $step++;
87             }
88             }
89             else {
90 0         0 return $x . q{.};
91             }
92              
93 1 50       3 if ( ! $ok ) {
94             #die "Can not happen: '$end'";
95 0         0 return;
96             }
97              
98             $end = $CARD2ORD{$end} || sub {
99 0     0   0 my $val = $CARD2ORDTR{$ok};
100 0         0 return $end . $val if $step == 1;
101 0         0 my $letter = (split RE_EMPTY, $val)[LAST_ELEMENT];
102 0         0 return $end.$letter.$val;
103 1   33     5 }->();
104              
105 1         4 return "$x$end";
106             }
107              
108             sub num2tr {
109 78     78 1 342 my $x = shift;
110 78 100 100     305 return unless defined $x and length $x;
111              
112 76 50       144 return 'sayı-değil' if $x eq 'NaN';
113 76 50       153 return 'eksi sonsuz' if $x =~ m/ \A \+ inf(?:inity)? \z /xmsi;
114 76 50       130 return 'artı sonsuz' if $x =~ m/ \A \- inf(?:inity)? \z /xmsi;
115 76 50       139 return 'sonsuz' if $x =~ m/ \A inf(?:inity)? \z /xmsi;
116 76 100       197 return $D{$x} if exists $D{$x}; # the most common cases
117              
118             # Make sure it's not in scientific notation:
119 55 100       62 { my $e = _e2tr($x); return $e if defined $e; }
  55         102  
  55         103  
120              
121 52         71 my $orig = $x;
122              
123 52         90 $x =~ s/,//xmsg; # nix any commas
124              
125 52         56 my $sign;
126 52 100       130 if ( $x =~ s/\A([-+])//xms ) {
127 11         21 $sign = $1;
128             }
129              
130 52         69 my($int, $fract);
131 52 100       172 if( $x =~ m/ \A \d+ \z/xms ) { $int = $x }
  38 100       50  
    100          
132 7         12 elsif( $x =~ m/ \A (\d+)[.](\d+) \z/xms ) { $int = $1; $fract = $2 }
  7         13  
133 3         6 elsif( $x =~ m/ \A [.](\d+) \z/xms ) { $fract = $1 }
134             else {
135 4         5 _log "Not a number: '$orig'\n" if DEBUG;
136 4         16 return;
137             }
138              
139             _log(
140             sprintf " Working on Sign[%s] Int2tr[%s] Fract[%s] < '%s'\n",
141 48         49 map { defined($_) ? $_ : 'nil' } $sign, $int, $fract, $orig
142             ) if DEBUG;
143              
144 48 100       119 return join SPACE, grep { defined $_ && length $_ }
  103         478  
145             _sign2tr( $sign ),
146             _int2tr( $int ),
147             _fract2tr( $fract ),
148             ;
149             }
150              
151             sub _sign2tr {
152 48     48   54 my $x = shift;
153 48 50 66     160 return ! defined $x || ! length $x ? undef
    100          
    100          
154             : $x eq q{-} ? 'eksi'
155             : $x eq q{+} ? 'artı'
156             : "WHAT_IS_$x"
157             ;
158             }
159              
160             sub _fract2tr { # "1234" => "point one two three four"
161 48     48   63 my $x = shift;
162 48 100 66     121 return unless defined $x and length $x;
163             return join SPACE, 'nokta',
164 10         28 map { $D{$_} }
  22         56  
165             split RE_EMPTY, $x;
166             }
167              
168             # The real work:
169              
170             sub _int2tr {
171 68     68   86 my $x = shift;
172 68 50 66     302 return unless defined $x and length $x and $x =~ m/\A\d+\z/xms;
      66        
173 65 100       142 return $D{$x} if defined $D{$x}; # most common/irreg cases
174              
175 47 100       120 if( $x =~ m/\A(.)(.)\z/xms ) {
    100          
176 31         149 return $D{$1 . '0'} . SPACE . $D{$2};
177             # like forty - two
178             # note that neither bit can be zero at this point
179             }
180             elsif ( $x =~ m/\A(.)(..)\z/xms ) {
181 9 100       27 my $tmp = $1 == 1 ? EMPTY_STRING : $D{$1} . SPACE;
182 9         25 my($h, $rest) = ($tmp.'yüz', $2);
183 9 100       20 return $h if $rest eq '00';
184 8         25 return "$h " . _int2tr(0 + $rest);
185             }
186             else {
187 7         13 return _bigint2tr($x);
188             }
189             }
190              
191             sub _bigint2tr {
192 7     7   14 my $x = shift;
193 7 50 33     37 return unless defined $x and length $x and $x =~ m/\A\d+\z/xms;
      33        
194 7         10 my @chunks; # each: [ string, exponent ]
195             {
196 7         9 my $groupnum = 0;
  7         9  
197 7         8 my $num;
198 7         30 while ( $x =~ s/(\d{1,3})\z//xms ) { # pull at most three digits from the end
199 16         35 $num = $1 + 0;
200 16 100       37 unshift @chunks, [ $num, $groupnum ] if $num;
201 16         41 ++$groupnum;
202             }
203 7 50       14 return $D{'0'} unless @chunks; # rare but possible
204             }
205              
206 7         9 my $and;
207             # junk
208 7 100 100     25 $and = EMPTY_STRING if $chunks[LAST_ELEMENT][1] == 0 and $chunks[LAST_ELEMENT][0] < CHUNK_MAX;
209             # The special 'and' that shows up in like "one thousand and eight"
210             # and "two billion and fifteen", but not "one thousand [*and] five hundred"
211             # or "one million, [*and] nine"
212              
213 7         18 _chunks2tr( \@chunks );
214              
215 7 50 33     14 $chunks[PREV_ELEMENT] .= SPACE if $and and @chunks > 1;
216 7 100       27 return "$chunks[0] $chunks[1]" if @chunks == 2;
217             # Avoid having a comma if just two units
218 2         7 return join q{, }, @chunks;
219             }
220              
221             sub _chunks2tr {
222 7     7   10 my $chunks = shift;
223 7 50       8 return if ! @{ $chunks };
  7         40  
224 7         12 my @out;
225 7         9 foreach my $c ( @{ $chunks } ) {
  7         11  
226 12 50       33 push @out, $c = _groupify( _int2tr( $c->[0] ), $c->[1] ,$c->[0]) if $c->[0];
227             }
228 7         11 @{ $chunks } = @out;
  7         21  
229 7         13 return;
230             }
231              
232             sub _groupify {
233             # turn ("seventeen", 3) => "seventeen billion"
234 12     12   31 my($basic, $multnum, $raw) = @_;
235 12 100       31 return $basic unless $multnum; # the first group is unitless
236 7         10 _log " Groupifying $basic x $multnum mults\n" if DEBUG > 2;
237              
238 7 50       18 if ( $MULT{$multnum} ) {
239 7 50 66     44 return $multnum == 1 && $raw == 1 ? $MULT{$multnum} : "$basic $MULT{$multnum}";
240             }
241             # Otherwise it must be huuuuuge, so fake it with scientific notation
242 0         0 return $basic . ' çarpı on üzeri ' . num2tr( $raw * 3 );
243             }
244              
245             # Because I can never remember this:
246             #
247             # 3.1E8
248             # ^^^ is called the "mantissa"
249             # ^ is called the "exponent"
250             # (the implicit "10" is the "base" a/k/a "radix")
251              
252             sub _e2tr {
253 55     55   67 my $x = shift;
254 55 100       175 if ( $x =~ RE_E2TR ) {
255 3         9 my($m, $e) = ($1, $2);
256 3         4 _log " Scientific notation: [$x] => $m E $e\n" if DEBUG;
257 3         7 $e += 0;
258 3         7 return num2tr($m) . ' çarpı on üzeri ' . num2tr($e);
259             }
260             else {
261 52         58 _log " Okay, $x isn't in exponential notation\n" if DEBUG;
262 52         90 return;
263             }
264             }
265              
266             sub _log {
267 0     0     my @args = @_;
268 0 0         print @args or croak "Unable to print to STDOUT: $!";
269 0           return;
270             }
271              
272             #==========================================================================
273              
274             1;
275              
276             =pod
277              
278             =encoding UTF-8
279              
280             =head1 NAME
281              
282             Lingua::TR::Numbers
283              
284             =head1 VERSION
285              
286             version 0.35
287              
288             =head1 SYNOPSIS
289              
290             use Lingua::TR::Numbers qw(num2tr num2tr_ordinal);
291            
292             my $x = 234;
293             my $y = 54;
294             print "Bugün yapman gereken ", num2tr($x), " tane işin var!\n";
295             print "Yarın annemin ", num2tr_ordinal($y), " yaşgününü kutlayacağız.\n";
296              
297             prints:
298              
299             Bugün yapman gereken iki yüz otuz dört tane işin var!
300             Yarın annemin elli dördüncü yaşgününü kutlayacağız.
301              
302             =head1 DESCRIPTION
303              
304             Lingua::TR::Numbers turns numbers into Turkish text. It exports
305             (upon request) two functions, C and C.
306             Each takes a scalar value and returns a scalar value. The return
307             value is the Turkish text expressing that number; or if what you
308             provided wasn't a number, then they return undef.
309              
310             This module can handle integers like "12" or "-3" and real numbers like "53.19".
311              
312             This module also understands exponential notation -- it turns "4E9" into
313             "dört çarpı 10 üzeri dokuz"). And it even turns "INF", "-INF", "NaN"
314             into "sonsuz", "eksi sonsuz" and "sayı-değil" respectively.
315              
316             Any commas in the input numbers are ignored.
317              
318             =head1 NAME
319              
320             Lingua::TR::Numbers - Converts numbers into Turkish text.
321              
322             =head1 FUNCTIONS
323              
324             You can import these one by one or use the special C<:all> tag:
325              
326             use Lingua::TR::Numbers qw(num2tr num2tr_ordinal);
327              
328             or
329              
330             use Lingua::TR::Numbers qw(:all);
331              
332             =head2 num2tr
333              
334             Converts the supplied number into Turkish text.
335              
336             =head2 num2tr_ordinal
337              
338             Similar to C, but returns ordinal versions .
339              
340             =head2 DEBUG
341              
342             Define C to enable debugging.
343              
344             =head1 LIMIT
345              
346             This module supports any numbers upto 999 decillion (999*10**33). Any further
347             range is currently not in commnon use and is not implemented.
348              
349             =head1 SEE ALSO
350              
351             L. L
352             L.
353              
354             See C (bundled with this distribution) for the Turkish translation of
355             this documentation.
356              
357             =head1 CAVEATS
358              
359             This module' s source file is UTF-8 encoded (without a BOM) and it returns UTF-8
360             values whenever possible.
361              
362             Currently, the module won't work with any Perl older than 5.6.
363              
364             =head1 ACKNOWLEDGEMENT
365              
366             This module is based on and includes modified code
367             portions from Sean M. Burke's Lingua::EN::Numbers.
368              
369             Lingua::EN::Numbers is Copyright (c) 2005, Sean M. Burke.
370              
371             =head1 AUTHOR
372              
373             Burak Gursoy
374              
375             =head1 COPYRIGHT AND LICENSE
376              
377             This software is copyright (c) 2006 by Burak Gursoy.
378              
379             This is free software; you can redistribute it and/or modify it under
380             the same terms as the Perl 5 programming language system itself.
381              
382             =cut
383              
384             __END__