File Coverage

blib/lib/Lingua/RU/Num2Word.pm
Criterion Covered Total %
statement 67 70 95.7
branch 23 30 76.6
condition 18 26 69.2
subroutine 8 8 100.0
pod 1 1 100.0
total 117 135 86.6


line stmt bran cond sub pod time code
1             package Lingua::RU::Num2Word;
2              
3 1     1   8482 use strict;
  1         1  
  1         25  
4 1     1   3 use warnings;
  1         1  
  1         16  
5 1     1   3 use utf8;
  1         0  
  1         6  
6 1     1   403 use POSIX qw/floor/;
  1         3920  
  1         4  
7 1     1   671 use Carp qw/croak/;
  1         1  
  1         42  
8              
9             # ABSTRACT: Numbers to words in russian (without currency, but with specified gender)
10             our $VERSION = '0.04'; # VERSION
11             # AUTHORITY
12              
13 1     1   3 use Exporter qw/import/;
  1         1  
  1         781  
14             our @EXPORT_OK = qw(&num2rus_cardinal);
15              
16             my %diw = (
17             0 => {
18             0 => { 0 => "ноль", 1 => 1 },
19             1 => { 0 => "", 1 => 2 },
20             2 => { 0 => "", 1 => 3 },
21             3 => { 0 => "три", 1 => 0 },
22             4 => { 0 => "четыре", 1 => 0 },
23             5 => { 0 => "пять", 1 => 1 },
24             6 => { 0 => "шесть", 1 => 1 },
25             7 => { 0 => "семь", 1 => 1 },
26             8 => { 0 => "восемь", 1 => 1 },
27             9 => { 0 => "девять", 1 => 1 },
28             10 => { 0 => "десять", 1 => 1 },
29             11 => { 0 => "одинадцать", 1 => 1 },
30             12 => { 0 => "двенадцать", 1 => 1 },
31             13 => { 0 => "тринадцать", 1 => 1 },
32             14 => { 0 => "четырнадцать", 1 => 1 },
33             15 => { 0 => "пятнадцать", 1 => 1 },
34             16 => { 0 => "шестнадцать", 1 => 1 },
35             17 => { 0 => "семнадцать", 1 => 1 },
36             18 => { 0 => "восемнадцать", 1 => 1 },
37             19 => { 0 => "девятнадцать", 1 => 1 },
38             },
39              
40             1 => {
41             2 => { 0 => "двадцать", 1 => 1 },
42             3 => { 0 => "тридцать", 1 => 1 },
43             4 => { 0 => "сорок", 1 => 1 },
44             5 => { 0 => "пятьдесят", 1 => 1 },
45             6 => { 0 => "шестьдесят", 1 => 1 },
46             7 => { 0 => "семьдесят", 1 => 1 },
47             8 => { 0 => "восемьдесят", 1 => 1 },
48             9 => { 0 => "девяносто", 1 => 1 },
49             },
50             2 => {
51             1 => { 0 => "сто", 1 => 1 },
52             2 => { 0 => "двести", 1 => 1 },
53             3 => { 0 => "триста", 1 => 1 },
54             4 => { 0 => "четыреста", 1 => 1 },
55             5 => { 0 => "пятьсот", 1 => 1 },
56             6 => { 0 => "шестьсот", 1 => 1 },
57             7 => { 0 => "семьсот", 1 => 1 },
58             8 => { 0 => "восемьсот", 1 => 1 },
59             9 => { 0 => "девятьсот", 1 => 1 }
60             }
61              
62             );
63              
64             my %nom = (
65             0 => { 0 => "", 1 => "", 2 => "одна", 3 => "две" },
66             1 => { 0 => "", 1 => "", 2 => "один", 3 => "два" },
67             2 => { 0 => "тысячи", 1 => "тысяч", 2 => "одна тысяча", 3 => "две тысячи" },
68             3 => {
69             0 => "миллиона",
70             1 => "миллионов",
71             2 => "один миллион",
72             3 => "два миллиона"
73             },
74             4 => {
75             0 => "миллиарда",
76             1 => "миллиардов",
77             2 => "один миллиард",
78             3 => "два миллиарда"
79             },
80             5 => {
81             0 => "триллиона",
82             1 => "триллионов",
83             2 => "один триллион",
84             3 => "два триллиона"
85             }
86             );
87              
88             my %genders = (
89             'FEMININE' => { 0 => "", 1 => "", 2 => "одна", 3 => "две" },
90             'MASCULINE' => { 0 => "", 1 => "", 2 => "один", 3 => "два" },
91             'NEUTER' => { 0 => "", 1 => "", 2 => "одно", 3 => "два" },
92             );
93              
94             # Stolen from Lingua::RU::Number
95              
96              
97             sub num2rus_cardinal {
98 117     117 1 37982 my ( $number, $gender ) = @_;
99              
100             # The biggest number we know about
101 117 100       214 if ($number > 999_999_999_999_999 ) {
102 1         3 return '';
103             }
104              
105 116   100     325 $gender ||= 'MASCULINE'; # masculine by default
106 116 100       583 croak "Wrong gender: $gender, should be MASCULINE|FEMININE|NEUTER" unless $gender =~ /masculine|feminine|neuter/i;
107 115         151 $gender = uc $gender;
108              
109 115 100       141 return _get_string( 0, 0, 0 ) unless $number; # no extra calculations for zero
110              
111 113         78 my ( $result, $negative );
112              
113             # Negative number, just add another word
114 113 50       144 if ( $number < 0 ) {
115 0         0 $number = abs( $number );
116 0         0 $negative = 1;
117             }
118              
119 113         85 $result = "";
120 113         261 my $int_number = floor( $number ); # no doubles
121              
122 113   66     421 for ( my $i = 1 ; $i < 6 && $int_number >= 1 ; $i++ ) {
123 116         115 my $tmp_number = $int_number / 1000;
124 116         868 my $number_part = sprintf( "%0.3f", $tmp_number - sprintf( "%d", $tmp_number ) ) * 1000;
125              
126 116         151 $int_number = floor $tmp_number;
127             # no doubles again
128 116         141 $result = _get_string( $number_part, $i, $gender ) . " " . $result;
129             }
130              
131             # Clean the result
132 113         326 $result =~ s/\s+/ /g;
133 113         280 $result =~ s/\s+$//;
134              
135 113 50       143 if ( $negative ) {
136 0         0 $result = "минус $result";
137             }
138              
139 113         197 return $result;
140             }
141              
142             sub _get_string {
143 118     118   95 my $sum = shift;
144 118 50       153 return unless defined $sum;
145              
146 118         95 my $nominal = shift;
147 118         80 my $gender = shift;
148 118         100 my ( $result, $nom ) = ( '', -1 );
149              
150 118 50 66     691 if ( ( !$nominal && $sum < 100 ) || ( $nominal > 0 && $nominal < 6 && $sum < 1000 ) ) {
      33        
      33        
      66        
151 118         172 my $s2 = sprintf( "%d", $sum / 100 );
152              
153 118 100       174 if ( $s2 > 0 ) { # hundreds
154 11         30 $result .= ' ' . $diw{2}{$s2}{0};
155 11         14 $nom = $diw{2}{$s2}{1};
156             }
157              
158 118         147 my $sx = $sum - $s2 * 100;
159              
160 118 100 100     390 if ( ( $sx < 20 && $sx > 0 ) || ( $sx == 0 && !$nominal ) ) {
      100        
      66        
161 24         59 $result .= " " . $diw{0}{$sx}{0};
162 24         34 $nom = $diw{0}{$sx}{1};
163             }
164             else {
165 94         144 my $s1 = floor $sx / 10; # tens
166              
167 94         159 my $s0 = sprintf( "%d", $sum - $s2 * 100 - $s1 * 10 + 0.5 );
168              
169 94 100       135 if ( $s1 > 0 ) {
170 93         235 $result .= ' ' . $diw{1}{$s1}{0};
171 93         106 $nom = $diw{1}{$s1}{1};
172             }
173 94 100       127 if ( $s0 > 0 ) {
174 85         152 $result .= ' ' . $diw{0}{$s0}{0};
175 85         118 $nom = $diw{0}{$s0}{1};
176             }
177             }
178             }
179              
180 118 50       153 if ( $nom >= 0 ) {
181              
182 118 100       132 if ( $nominal == 1 ) {
183 113 50       253 $result .= defined $nominal ? ' ' . $genders{$gender}{$nom} : '';
184             }
185             else {
186 5 50       13 $result .= defined $nominal ? ' ' . $nom{$nominal}{$nom} : '';
187             }
188             }
189 118         500 $result =~ s/^\s*//g;
190 118         488 $result =~ s/\s*$//g;
191              
192 118         594 return $result;
193             }
194              
195             1;
196              
197             __END__