File Coverage

blib/lib/Lingua/RUS/Num2Word.pm
Criterion Covered Total %
statement 75 160 46.8
branch 20 86 23.2
condition 23 52 44.2
subroutine 12 16 75.0
pod 4 5 80.0
total 134 319 42.0


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8 -*-
2             #
3             # (c) 2002-2010 PetaMem, s.r.o.
4             #
5              
6             package Lingua::RUS::Num2Word;
7             # ABSTRACT: Converts numbers to money sum in words (in Russian roubles)
8              
9 1     1   124423 use 5.16.0;
  1         19  
10 1     1   8 use utf8;
  1         2  
  1         12  
11 1     1   97 use warnings;
  1         2  
  1         63  
12              
13             # {{{ use block
14              
15 1     1   10 use Carp;
  1         4  
  1         94  
16 1     1   801 use Export::Attrs;
  1         10560  
  1         6  
17              
18             # }}}
19             # {{{ variables declaration
20             our $VERSION = '0.2603300';
21              
22             # Preloaded methods go here.
23 1     1   91 use vars qw(%diw %nom);
  1         2  
  1         434  
24              
25             %diw = (
26             0 => {
27             0 => { 0 => "ноль", 1 => 1},
28             1 => { 0 => "", 1 => 2},
29             2 => { 0 => "", 1 => 3},
30             3 => { 0 => "три", 1 => 0},
31             4 => { 0 => "четыре", 1 => 0},
32             5 => { 0 => "пять", 1 => 1},
33             6 => { 0 => "шесть", 1 => 1},
34             7 => { 0 => "семь", 1 => 1},
35             8 => { 0 => "восемь", 1 => 1},
36             9 => { 0 => "девять", 1 => 1},
37             10 => { 0 => "десять", 1 => 1},
38             11 => { 0 => "одинадцать", 1 => 1},
39             12 => { 0 => "двенадцать", 1 => 1},
40             13 => { 0 => "тринадцать", 1 => 1},
41             14 => { 0 => "четырнадцать", 1 => 1},
42             15 => { 0 => "пятнадцать", 1 => 1},
43             16 => { 0 => "шестнадцать", 1 => 1},
44             17 => { 0 => "семнадцать", 1 => 1},
45             18 => { 0 => "восемнадцать", 1 => 1},
46             19 => { 0 => "девятнадцать", 1 => 1},
47             },
48             1 => {
49             2 => { 0 => "двадцать", 1 => 1},
50             3 => { 0 => "тридцать", 1 => 1},
51             4 => { 0 => "сорок", 1 => 1},
52             5 => { 0 => "пятьдесят", 1 => 1},
53             6 => { 0 => "шестьдесят", 1 => 1},
54             7 => { 0 => "семьдесят", 1 => 1},
55             8 => { 0 => "восемьдесят", 1 => 1},
56             9 => { 0 => "девяносто", 1 => 1},
57             },
58             2 => {
59             1 => { 0 => "сто", 1 => 1},
60             2 => { 0 => "двести", 1 => 1},
61             3 => { 0 => "триста", 1 => 1},
62             4 => { 0 => "четыреста", 1 => 1},
63             5 => { 0 => "пятьсот", 1 => 1},
64             6 => { 0 => "шестьсот", 1 => 1},
65             7 => { 0 => "семьсот", 1 => 1},
66             8 => { 0 => "восемьсот", 1 => 1},
67             9 => { 0 => "девятьсот", 1 => 1}
68             }
69             );
70              
71             %nom = (
72             0 => {0 => "копейки", 1 => "копеек", 2 => "одна копейка", 3 => "две копейки"},
73             1 => {0 => "рубля", 1 => "рублей", 2 => "один рубль", 3 => "два рубля"},
74             2 => {0 => "тысячи", 1 => "тысяч", 2 => "одна тысяча", 3 => "две тысячи"},
75             3 => {0 => "миллиона", 1 => "миллионов", 2 => "один миллион", 3 => "два миллиона"},
76             4 => {0 => "миллиарда",1 => "миллиардов",2 => "один миллиард",3 => "два миллиарда"},
77             5 => {0 => "триллиона",1 => "триллионов",2 => "один триллион",3 => "два триллиона"}
78             );
79              
80             my $out_rub;
81              
82             # }}}
83              
84             # {{{ rur_in_words
85              
86 1     1 0 7 sub num2rus_cardinal :Export { goto &rur_in_words }
  1     0   1  
  1         6  
  0         0  
87              
88             sub rur_in_words :Export {
89 6   100 6 1 173974 my ($sum) = shift // 0;
90 6         10 my ($retval, $i, $sum_rub, $sum_kop);
91              
92 6         9 $retval = "";
93 6 100       14 $out_rub = ($sum >= 1) ? 0 : 1;
94 6         17 $sum_rub = sprintf("%d", $sum);
95 6 50       15 $sum_rub-- if (($sum_rub - $sum) > 0);
96 6         36 $sum_kop = sprintf("%0.2f", ($sum - $sum_rub)) * 100;
97 6         22 my $kop = get_string($sum_kop, 0);
98              
99 6   66     20 for ($i=1; $i<6 && $sum_rub >= 1; $i++) {
100 4         5 my $sum_tmp = $sum_rub / 1000;
101 4         22 my $sum_part = sprintf("%0.3f", $sum_tmp - sprintf("%d", $sum_tmp) ) * 1000;
102 4         7 $sum_rub = sprintf("%d", $sum_tmp);
103              
104 4 50       14 $sum_rub-- if ($sum_rub - $sum_tmp > 0);
105 4         7 $retval = get_string($sum_part, $i)." ".$retval;
106             }
107 6 50       9 $retval .= " рублей" if ($out_rub == 0);
108 6         9 $retval .= " ".$kop;
109 6         29 $retval =~ s/\s+/ /g;
110              
111 6         14 return $retval;
112 1     1   423 }
  1         2  
  1         4  
113              
114             # }}}
115             # {{{ get_string
116              
117             sub get_string :Export{
118 17   100 17 1 3827 my $sum = shift // return;
119 15         15 my $nominal = shift;
120 15         27 my ($retval, $nom) = ('', -1);
121              
122 15 50 66     67 if ((!$nominal && $sum < 100) || ($nominal > 0 && $nominal < 6 && $sum < 1000)) {
      33        
      33        
      66        
123 15         33 my $s2 = sprintf("%d", $sum / 100);
124 15 100       38 if ($s2 > 0) {
125 2         5 $retval .= ' '.$diw{2}{$s2}{0};
126 2         3 $nom = $diw{2}{$s2}{1};
127             }
128 15         25 my $sx = sprintf("%d", $sum - $s2 * 100);
129 15 50       29 $sx-- if ($sx - ($sum - $s2*100) > 0);
130              
131 15 100 100     75 if (($sx<20 && $sx>0) || ($sx == 0 && !$nominal)) {
      66        
      100        
132 12         29 $retval .= " ".$diw{0}{$sx}{0};
133 12         19 $nom = $diw{0}{$sx}{1};
134             } else {
135 3         5 my $s1 = sprintf("%d", $sx / 10);
136 3 50       9 $s1-- if (($s1 - $sx/10) > 0);
137 3         5 my $s0 = sprintf("%d", $sum - $s2*100 - $s1*10 + 0.5);
138 3 50       6 if ($s1 > 0) {
139 3         8 $retval .= ' '.$diw{1}{$s1}{0};
140 3         4 $nom = $diw{1}{$s1}{1};
141             }
142 3 100       6 if ($s0 > 0) {
143 2         4 $retval .= ' '.$diw{0}{$s0}{0};
144 2         3 $nom = $diw{0}{$s0}{1};
145             }
146             }
147             }
148 15 50       24 if ($nom >= 0) {
149 15 100       58 $retval .= defined $nominal ? ' '.$nom{$nominal}{$nom} : '';
150 15 100 100     55 $out_rub = 1 if (defined $nominal && $nominal == 1);
151             }
152 15         92 $retval =~ s/^\s*//g;
153 15         78 $retval =~ s/\s*$//g;
154              
155 15         50 return $retval;
156 1     1   676 }
  1         2  
  1         4  
157              
158             # }}}
159              
160              
161             # {{{ num2rus_ordinal number to ordinal string conversion
162              
163             sub num2rus_ordinal :Export {
164 0     0 1   my $number = shift;
165              
166 0 0 0       croak 'You should specify a number from interval [0, 999_999_999]'
      0        
      0        
167             if !defined $number
168             || $number !~ m{\A\d+\z}xms
169             || $number < 0
170             || $number > 999_999_999;
171              
172             # Irregular ordinals 0-10
173 0           my %irregular = (
174             0 => 'нулевой',
175             1 => 'первый',
176             2 => 'второй',
177             3 => 'третий',
178             4 => 'четвёртый',
179             5 => 'пятый',
180             6 => 'шестой',
181             7 => 'седьмой',
182             8 => 'восьмой',
183             9 => 'девятый',
184             10 => 'десятый',
185             );
186              
187 0 0         return $irregular{$number} if exists $irregular{$number};
188              
189             # Teens ordinals 11-19
190 0           my %teens = (
191             11 => 'одиннадцатый',
192             12 => 'двенадцатый',
193             13 => 'тринадцатый',
194             14 => 'четырнадцатый',
195             15 => 'пятнадцатый',
196             16 => 'шестнадцатый',
197             17 => 'семнадцатый',
198             18 => 'восемнадцатый',
199             19 => 'девятнадцатый',
200             );
201              
202 0 0         return $teens{$number} if exists $teens{$number};
203              
204             # Tens ordinals
205 0           my %tens_ord = (
206             20 => 'двадцатый',
207             30 => 'тридцатый',
208             40 => 'сороковой',
209             50 => 'пятидесятый',
210             60 => 'шестидесятый',
211             70 => 'семидесятый',
212             80 => 'восьмидесятый',
213             90 => 'девяностый',
214             );
215              
216             # Cardinal tens (for compound numbers)
217 0           my %tens_card = (
218             20 => 'двадцать',
219             30 => 'тридцать',
220             40 => 'сорок',
221             50 => 'пятьдесят',
222             60 => 'шестьдесят',
223             70 => 'семьдесят',
224             80 => 'восемьдесят',
225             90 => 'девяносто',
226             );
227              
228             # Hundreds ordinals
229 0           my %hundreds_ord = (
230             100 => 'сотый',
231             200 => 'двухсотый',
232             300 => 'трёхсотый',
233             400 => 'четырёхсотый',
234             500 => 'пятисотый',
235             600 => 'шестисотый',
236             700 => 'семисотый',
237             800 => 'восьмисотый',
238             900 => 'девятисотый',
239             );
240              
241             # Cardinal hundreds (for compound numbers)
242 0           my %hundreds_card = (
243             100 => 'сто',
244             200 => 'двести',
245             300 => 'триста',
246             400 => 'четыреста',
247             500 => 'пятьсот',
248             600 => 'шестьсот',
249             700 => 'семьсот',
250             800 => 'восемьсот',
251             900 => 'девятьсот',
252             );
253              
254             # For numbers >= 1_000_000
255 0 0         if ($number >= 1_000_000) {
256 0           my $millions = int($number / 1_000_000);
257 0           my $remainder = $number % 1_000_000;
258 0 0         if ($remainder == 0) {
259 0 0         if ($millions == 1) {
260 0           return 'миллионный';
261             }
262 0           return _rus_cardinal($millions) . ' миллионный';
263             }
264 0           my $prefix = _rus_cardinal($millions);
265 0           my $tmp4 = $millions % 10;
266 0           my $tmp3 = $millions % 100;
267 0           my $mil_word;
268 0 0 0       if ($tmp3 >= 11 && $tmp3 <= 19) {
    0 0        
    0          
269 0           $mil_word = 'миллионов';
270             }
271             elsif ($tmp4 == 1) {
272 0           $mil_word = 'миллион';
273             }
274             elsif ($tmp4 >= 2 && $tmp4 <= 4) {
275 0           $mil_word = 'миллиона';
276             }
277             else {
278 0           $mil_word = 'миллионов';
279             }
280 0           return $prefix . ' ' . $mil_word . ' ' . num2rus_ordinal($remainder);
281             }
282              
283 0 0         if ($number >= 1_000) {
284 0           my $thousands = int($number / 1_000);
285 0           my $remainder = $number % 1_000;
286 0 0         if ($remainder == 0) {
287 0 0         if ($thousands == 1) {
288 0           return 'тысячный';
289             }
290 0           return _rus_cardinal($thousands) . ' тысячный';
291             }
292 0           my $tmp4 = $thousands % 10;
293 0           my $tmp3 = $thousands % 100;
294 0           my $thou_cardinal;
295 0 0 0       if ($thousands == 1) {
    0 0        
    0          
    0          
    0          
    0          
296 0           $thou_cardinal = 'тысяча';
297             }
298             elsif ($thousands == 2) {
299 0           $thou_cardinal = 'две тысячи';
300             }
301             elsif ($tmp3 >= 11 && $tmp3 <= 19) {
302 0           $thou_cardinal = _rus_cardinal($thousands) . ' тысяч';
303             }
304             elsif ($tmp4 == 1) {
305 0           $thou_cardinal = _rus_cardinal($thousands - 1) . ' одна тысяча';
306             }
307             elsif ($tmp4 == 2) {
308 0           $thou_cardinal = _rus_cardinal($thousands - 2) . ' две тысячи';
309             }
310             elsif ($tmp4 >= 3 && $tmp4 <= 4) {
311 0           $thou_cardinal = _rus_cardinal($thousands) . ' тысячи';
312             }
313             else {
314 0           $thou_cardinal = _rus_cardinal($thousands) . ' тысяч';
315             }
316 0           $thou_cardinal =~ s{^\s+}{};
317 0           return $thou_cardinal . ' ' . num2rus_ordinal($remainder);
318             }
319              
320 0 0         if ($number >= 100) {
321 0           my $h = int($number / 100) * 100;
322 0           my $remainder = $number % 100;
323 0 0         if ($remainder == 0) {
324 0           return $hundreds_ord{$h};
325             }
326 0           return $hundreds_card{$h} . ' ' . num2rus_ordinal($remainder);
327             }
328              
329             # 20-99 compound
330 0 0         if ($number >= 20) {
331 0           my $t = int($number / 10) * 10;
332 0           my $remainder = $number % 10;
333 0 0         if ($remainder == 0) {
334 0           return $tens_ord{$t};
335             }
336 0           return $tens_card{$t} . ' ' . $irregular{$remainder};
337             }
338              
339             # Should not reach here
340 0           return;
341 1     1   947 }
  1         2  
  1         5  
342              
343             # }}}
344              
345             # {{{ _rus_cardinal internal: number to cardinal words (for ordinal composition)
346              
347             sub _rus_cardinal {
348 0     0     my ($number) = @_;
349              
350 0 0         return '' if $number == 0;
351              
352 0           my %card_units = (
353             1 => 'один', 2 => 'два', 3 => 'три',
354             4 => 'четыре', 5 => 'пять', 6 => 'шесть',
355             7 => 'семь', 8 => 'восемь', 9 => 'девять',
356             10 => 'десять',
357             11 => 'одиннадцать', 12 => 'двенадцать',
358             13 => 'тринадцать', 14 => 'четырнадцать',
359             15 => 'пятнадцать', 16 => 'шестнадцать',
360             17 => 'семнадцать', 18 => 'восемнадцать',
361             19 => 'девятнадцать',
362             );
363              
364 0           my %card_tens = (
365             20 => 'двадцать', 30 => 'тридцать',
366             40 => 'сорок', 50 => 'пятьдесят',
367             60 => 'шестьдесят', 70 => 'семьдесят',
368             80 => 'восемьдесят', 90 => 'девяносто',
369             );
370              
371 0           my %card_hundreds = (
372             100 => 'сто', 200 => 'двести',
373             300 => 'триста', 400 => 'четыреста',
374             500 => 'пятьсот', 600 => 'шестьсот',
375             700 => 'семьсот', 800 => 'восемьсот',
376             900 => 'девятьсот',
377             );
378              
379 0 0         return $card_units{$number} if exists $card_units{$number};
380              
381 0           my $result = '';
382              
383 0 0         if ($number >= 100) {
384 0           my $h = int($number / 100) * 100;
385 0           $result = $card_hundreds{$h};
386 0           $number %= 100;
387 0 0         return $result if $number == 0;
388 0           $result .= ' ';
389             }
390              
391 0 0         if ($number >= 20) {
392 0           my $t = int($number / 10) * 10;
393 0           $result .= $card_tens{$t};
394 0           $number %= 10;
395 0 0         return $result if $number == 0;
396 0           $result .= ' ' . $card_units{$number};
397 0           return $result;
398             }
399              
400 0 0         if ($number > 0) {
401 0           $result .= $card_units{$number};
402             }
403              
404 0           return $result;
405             }
406              
407             # }}}
408              
409             # {{{ capabilities declare supported features
410              
411             sub capabilities {
412             return {
413 0     0 1   cardinal => 1,
414             ordinal => 1,
415             };
416             }
417              
418             # }}}
419             1;
420              
421             __END__