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