File Coverage

blib/lib/Lingua/RUS/Num2Word.pm
Criterion Covered Total %
statement 116 158 73.4
branch 42 84 50.0
condition 26 52 50.0
subroutine 14 16 87.5
pod 4 5 80.0
total 202 315 64.1


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