File Coverage

blib/lib/Lingua/RU/Num2Word.pm
Criterion Covered Total %
statement 65 68 95.5
branch 22 28 78.5
condition 19 26 73.0
subroutine 8 8 100.0
pod 1 1 100.0
total 115 131 87.7


line stmt bran cond sub pod time code
1             package Lingua::RU::Num2Word;
2              
3 1     1   8301 use strict;
  1         2  
  1         24  
4 1     1   2 use warnings;
  1         1  
  1         14  
5 1     1   3 use utf8;
  1         1  
  1         6  
6 1     1   374 use POSIX qw/floor/;
  1         3688  
  1         5  
7 1     1   631 use Carp qw/croak/;
  1         2  
  1         39  
8              
9             # ABSTRACT: Numbers to words in russian (without currency, but with specified gender)
10             our $VERSION = '0.03'; # VERSION
11             # AUTHORITY
12              
13 1     1   3 use Exporter qw/import/;
  1         1  
  1         783  
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 26     26 1 9095 my ( $number, $gender ) = @_;
99              
100 26   100     66 $gender ||= 'MASCULINE'; # masculine by default
101 26 100       154 croak "Wrong gender: $gender, should be MASCULINE|FEMININE|NEUTER" unless $gender =~ /masculine|feminine|neuter/i;
102 25         32 $gender = uc $gender;
103              
104 25 100       34 return _get_string( 0, 0, 0 ) unless $number; # no extra calculations for zero
105              
106 24         13 my ( $result, $negative );
107              
108             # Negative number, just add another word
109 24 50       31 if ( $number < 0 ) {
110 0         0 $number = abs( $number );
111 0         0 $negative = 1;
112             }
113              
114 24         14 $result = "";
115 24         61 my $int_number = floor( $number ); # no doubles
116              
117 24   100     77 for ( my $i = 1 ; $i < 6 && $int_number >= 1 ; $i++ ) {
118 31         19 my $tmp_number = $int_number / 1000;
119 31         196 my $number_part = sprintf( "%0.3f", $tmp_number - sprintf( "%d", $tmp_number ) ) * 1000;
120              
121 31         37 $int_number = floor $tmp_number; # no doubles again
122 31         32 $result = _get_string( $number_part, $i, $gender ) . " " . $result;
123             }
124              
125             # Clean the result
126 24         58 $result =~ s/\s+/ /g;
127 24         54 $result =~ s/\s+$//;
128              
129 24 50       28 if ( $negative ) {
130 0         0 $result = "минус $result";
131             }
132              
133 24         34 return $result;
134             }
135              
136             sub _get_string {
137 32     32   23 my $sum = shift;
138 32 50       40 return unless defined $sum;
139              
140 32         23 my $nominal = shift;
141 32         19 my $gender = shift;
142 32         28 my ( $result, $nom ) = ( '', -1 );
143              
144 32 50 66     165 if ( ( !$nominal && $sum < 100 ) || ( $nominal > 0 && $nominal < 6 && $sum < 1000 ) ) {
      33        
      33        
      66        
145 32         74 my $s2 = sprintf( "%d", $sum / 100 );
146              
147 32 100       42 if ( $s2 > 0 ) { # hundreds
148 9         20 $result .= ' ' . $diw{2}{$s2}{0};
149 9         9 $nom = $diw{2}{$s2}{1};
150             }
151              
152 32         43 my $sx = floor $sum - $s2 * 100;
153              
154 32 100 100     116 if ( ( $sx < 20 && $sx > 0 ) || ( $sx == 0 && !$nominal ) ) {
      100        
      66        
155 15         34 $result .= " " . $diw{0}{$sx}{0};
156 15         19 $nom = $diw{0}{$sx}{1};
157             }
158             else {
159 17         16 my $s1 = floor $sx / 10; # tens
160              
161 17         27 my $s0 = sprintf( "%d", $sum - $s2 * 100 - $s1 * 10 + 0.5 );
162              
163 17 100       18 if ( $s1 > 0 ) {
164 12         21 $result .= ' ' . $diw{1}{$s1}{0};
165 12         12 $nom = $diw{1}{$s1}{1};
166             }
167 17 100       23 if ( $s0 > 0 ) {
168 12         16 $result .= ' ' . $diw{0}{$s0}{0};
169 12         14 $nom = $diw{0}{$s0}{1};
170             }
171             }
172             }
173 32 100       50 if ( $nom >= 0 ) {
174              
175 27 100       26 if ( $nominal == 1 ) {
176 23 50       46 $result .= defined $nominal ? ' ' . $genders{$gender}{$nom} : '';
177             }
178             else {
179 4 50       11 $result .= defined $nominal ? ' ' . $nom{$nominal}{$nom} : '';
180             }
181             }
182 32         101 $result =~ s/^\s*//g;
183 32         111 $result =~ s/\s*$//g;
184              
185 32         135 return $result;
186             }
187              
188             1;
189              
190             __END__