File Coverage

blib/lib/Lingua/UKR/Num2Word.pm
Criterion Covered Total %
statement 27 117 23.0
branch 2 78 2.5
condition 3 48 6.2
subroutine 8 10 80.0
pod 3 3 100.0
total 43 256 16.8


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; mode:folding; coding:utf-8 -*-
2              
3             package Lingua::UKR::Num2Word;
4             # ABSTRACT: Number to word conversion in Ukrainian
5              
6 1     1   77590 use 5.16.0;
  1         3  
7 1     1   4 use utf8;
  1         2  
  1         8  
8 1     1   20 use warnings;
  1         1  
  1         46  
9              
10             # {{{ use block
11              
12 1     1   4 use Carp;
  1         6  
  1         57  
13 1     1   469 use Export::Attrs;
  1         8125  
  1         6  
14              
15             # }}}
16             # {{{ var block
17             our $VERSION = '0.2603300';
18             my %token1 = (
19             0 => 'нуль',
20             1 => 'один', 2 => 'два',
21             3 => 'три', 4 => 'чотири',
22             5 => "п'ять", 6 => 'шість',
23             7 => 'сім', 8 => 'вісім',
24             9 => "дев'ять", 10 => 'десять',
25             11 => 'одинадцять', 12 => 'дванадцять',
26             13 => 'тринадцять', 14 => 'чотирнадцять',
27             15 => "п'ятнадцять", 16 => 'шістнадцять',
28             17 => 'сімнадцять', 18 => 'вісімнадцять',
29             19 => "дев'ятнадцять",
30             );
31             my %token2 = (
32             20 => 'двадцять', 30 => 'тридцять',
33             40 => 'сорок', 50 => "п'ятдесят",
34             60 => 'шістдесят', 70 => 'сімдесят',
35             80 => 'вісімдесят', 90 => "дев'яносто",
36             );
37             my %token3 = (
38             100 => 'сто', 200 => 'двісті',
39             300 => 'триста', 400 => 'чотириста',
40             500 => "п'ятсот", 600 => 'шістсот',
41             700 => 'сімсот', 800 => 'вісімсот',
42             900 => "дев'ятсот",
43             );
44              
45             # }}}
46              
47             # {{{ num2ukr_cardinal number to string conversion
48              
49             sub num2ukr_cardinal :Export {
50 2     2 1 126224 my $result = '';
51 2         3 my $number = shift;
52              
53 2 50 33     22 croak 'You should specify a number from interval [0, 999_999_999]'
      33        
      33        
54             if !defined $number
55             || $number !~ m{\A\d+\z}xms
56             || $number < 0
57             || $number > 999_999_999;
58              
59 2         3 my $reminder = 0;
60              
61 2 50       6 if ($number < 20) {
    0          
    0          
    0          
    0          
62 2         4 $result = $token1{$number};
63             }
64             elsif ($number < 100) {
65 0         0 $reminder = $number % 10;
66 0 0       0 if ($reminder == 0) {
67 0         0 $result = $token2{$number};
68             }
69             else {
70 0         0 $result = $token2{$number - $reminder}.' '.num2ukr_cardinal($reminder);
71             }
72             }
73             elsif ($number < 1_000) {
74 0         0 $reminder = $number % 100;
75 0 0       0 if ($reminder != 0) {
76 0         0 $result = $token3{$number - $reminder}.' '.num2ukr_cardinal($reminder);
77             }
78             else {
79 0         0 $result = $token3{$number};
80             }
81             }
82             elsif ($number < 1_000_000) {
83 0         0 $reminder = $number % 1_000;
84 0 0       0 my $tmp1 = ($reminder != 0) ? ' '.num2ukr_cardinal($reminder) : '';
85 0         0 my $tmp2 = substr($number, 0, length($number)-3);
86 0         0 my $tmp3 = $tmp2 % 100;
87 0         0 my $tmp4 = $tmp2 % 10;
88              
89 0 0 0     0 if ($tmp3 < 9 || $tmp3 > 20) {
90 0 0 0     0 if ($tmp4 == 1 && $tmp2 == 1) {
    0 0        
    0 0        
    0          
    0          
91 0         0 $tmp2 = 'тисяча';
92             }
93             elsif ($tmp4 == 1) {
94 0         0 $tmp2 = num2ukr_cardinal($tmp2 - $tmp4).' одна тисяча';
95             }
96             elsif ($tmp4 == 2 && $tmp2 == 2) {
97 0         0 $tmp2 = 'дві тисячі';
98             }
99             elsif ($tmp4 == 2) {
100 0         0 $tmp2 = num2ukr_cardinal($tmp2 - $tmp4).' дві тисячі';
101             }
102             elsif ($tmp4 > 2 && $tmp4 < 5) {
103 0         0 $tmp2 = num2ukr_cardinal($tmp2).' тисячі';
104             }
105             else {
106 0         0 $tmp2 = num2ukr_cardinal($tmp2).' тисяч';
107             }
108             }
109             else {
110 0         0 $tmp2 = num2ukr_cardinal($tmp2).' тисяч';
111             }
112 0         0 $result = $tmp2.$tmp1;
113             }
114             elsif ($number < 1_000_000_000) {
115 0         0 $reminder = $number % 1_000_000;
116 0 0       0 my $tmp1 = ($reminder != 0) ? ' '.num2ukr_cardinal($reminder) : '';
117 0         0 my $tmp2 = substr($number, 0, length($number)-6);
118 0         0 my $tmp3 = $tmp2 % 100;
119 0         0 my $tmp4 = $tmp2 % 10;
120              
121 0 0 0     0 if ($tmp3 < 9 || $tmp3 > 20) {
122 0 0 0     0 if ($tmp4 == 1 && $tmp2 == 1) {
    0 0        
    0          
123 0         0 $tmp2 = 'мільйон';
124             }
125             elsif ($tmp4 == 1) {
126 0         0 $tmp2 = num2ukr_cardinal($tmp2 - $tmp4).' один мільйон';
127             }
128             elsif ($tmp4 > 1 && $tmp4 < 5) {
129 0         0 $tmp2 = num2ukr_cardinal($tmp2).' мільйони';
130             }
131             else {
132 0         0 $tmp2 = num2ukr_cardinal($tmp2).' мільйонів';
133             }
134             }
135             else {
136 0         0 $tmp2 = num2ukr_cardinal($tmp2).' мільйонів';
137             }
138              
139 0         0 $result = $tmp2.$tmp1;
140             }
141              
142 2         4 return $result;
143 1     1   550 }
  1         2  
  1         6  
144              
145             # }}}
146              
147             # {{{ num2ukr_ordinal number to ordinal string conversion
148              
149             sub num2ukr_ordinal :Export {
150 0     0 1   my $number = shift;
151              
152 0 0 0       croak 'You should specify a number from interval [0, 999_999_999]'
      0        
      0        
153             if !defined $number
154             || $number !~ m{\A\d+\z}xms
155             || $number < 0
156             || $number > 999_999_999;
157              
158             # Irregular ordinals 0-10
159 0           my %irregular = (
160             0 => 'нульовий',
161             1 => 'перший',
162             2 => 'другий',
163             3 => 'третій',
164             4 => 'четвертий',
165             5 => "п'ятий",
166             6 => 'шостий',
167             7 => 'сьомий',
168             8 => 'восьмий',
169             9 => "дев'ятий",
170             10 => 'десятий',
171             );
172              
173 0 0         return $irregular{$number} if exists $irregular{$number};
174              
175             # Teens ordinals 11-19
176 0           my %teens = (
177             11 => 'одинадцятий',
178             12 => 'дванадцятий',
179             13 => 'тринадцятий',
180             14 => 'чотирнадцятий',
181             15 => "п'ятнадцятий",
182             16 => 'шістнадцятий',
183             17 => 'сімнадцятий',
184             18 => 'вісімнадцятий',
185             19 => "дев'ятнадцятий",
186             );
187              
188 0 0         return $teens{$number} if exists $teens{$number};
189              
190             # Tens ordinals
191 0           my %tens_ord = (
192             20 => 'двадцятий',
193             30 => 'тридцятий',
194             40 => 'сороковий',
195             50 => "п'ятдесятий",
196             60 => 'шістдесятий',
197             70 => 'сімдесятий',
198             80 => 'вісімдесятий',
199             90 => "дев'яностий",
200             );
201              
202             # Hundreds ordinals
203 0           my %hundreds_ord = (
204             100 => 'сотий',
205             200 => 'двохсотий',
206             300 => 'трьохсотий',
207             400 => 'чотирьохсотий',
208             500 => "п'ятисотий",
209             600 => 'шестисотий',
210             700 => 'семисотий',
211             800 => 'восьмисотий',
212             900 => "дев'ятисотий",
213             );
214              
215             # For numbers >= 1_000_000
216 0 0         if ($number >= 1_000_000) {
217 0           my $millions = int($number / 1_000_000);
218 0           my $remainder = $number % 1_000_000;
219 0 0         if ($remainder == 0) {
220 0 0         if ($millions == 1) {
221 0           return 'мільйонний';
222             }
223 0           return num2ukr_cardinal($millions) . ' мільйонний';
224             }
225 0           my $prefix = num2ukr_cardinal($millions);
226 0           my $tmp4 = $millions % 10;
227 0           my $tmp3 = $millions % 100;
228 0           my $mil_word;
229 0 0 0       if ($tmp3 < 9 || $tmp3 > 20) {
230 0 0 0       if ($tmp4 == 1) {
    0          
231 0           $mil_word = 'мільйон';
232             }
233             elsif ($tmp4 > 1 && $tmp4 < 5) {
234 0           $mil_word = 'мільйони';
235             }
236             else {
237 0           $mil_word = 'мільйонів';
238             }
239             }
240             else {
241 0           $mil_word = 'мільйонів';
242             }
243 0           return $prefix . ' ' . $mil_word . ' ' . num2ukr_ordinal($remainder);
244             }
245              
246 0 0         if ($number >= 1_000) {
247 0           my $thousands = int($number / 1_000);
248 0           my $remainder = $number % 1_000;
249 0 0         if ($remainder == 0) {
250 0 0         if ($thousands == 1) {
251 0           return 'тисячний';
252             }
253 0           return num2ukr_cardinal($thousands) . ' тисячний';
254             }
255 0           my $thou_cardinal;
256 0 0 0       if ($thousands == 1) {
    0          
    0          
257 0           $thou_cardinal = 'тисяча';
258             }
259             elsif ($thousands == 2) {
260 0           $thou_cardinal = 'дві тисячі';
261             }
262             elsif ($thousands >= 3 && $thousands <= 4) {
263 0           $thou_cardinal = num2ukr_cardinal($thousands) . ' тисячі';
264             }
265             else {
266 0           $thou_cardinal = num2ukr_cardinal($thousands) . ' тисяч';
267             }
268 0           return $thou_cardinal . ' ' . num2ukr_ordinal($remainder);
269             }
270              
271 0 0         if ($number >= 100) {
272 0           my $h = int($number / 100) * 100;
273 0           my $remainder = $number % 100;
274 0 0         if ($remainder == 0) {
275 0           return $hundreds_ord{$h};
276             }
277 0           return $token3{$h} . ' ' . num2ukr_ordinal($remainder);
278             }
279              
280             # 20-99 compound
281 0 0         if ($number >= 20) {
282 0           my $t = int($number / 10) * 10;
283 0           my $remainder = $number % 10;
284 0 0         if ($remainder == 0) {
285 0           return $tens_ord{$t};
286             }
287 0           return $tens_ord{$t} . ' ' . $irregular{$remainder};
288             }
289              
290             # Should not reach here
291 0           return;
292 1     1   715 }
  1         1  
  1         4  
293              
294             # }}}
295              
296             # {{{ capabilities declare supported features
297              
298             sub capabilities {
299             return {
300 0     0 1   cardinal => 1,
301             ordinal => 1,
302             };
303             }
304              
305             # }}}
306             1;
307              
308             __END__