File Coverage

blib/lib/Lingua/LAT/Num2Word.pm
Criterion Covered Total %
statement 56 95 58.9
branch 23 54 42.5
condition 19 54 35.1
subroutine 9 11 81.8
pod 3 3 100.0
total 110 217 50.6


line stmt bran cond sub pod time code
1             # For Emacs: -*- mode:cperl; eval: (folding-mode 1); coding:utf-8 -*-
2              
3             package Lingua::LAT::Num2Word;
4             # ABSTRACT: Number to word conversion in Latin
5              
6 1     1   117654 use 5.16.0;
  1         3  
7 1     1   4 use utf8;
  1         1  
  1         9  
8 1     1   20 use warnings;
  1         3  
  1         49  
9              
10             # {{{ use block
11              
12 1     1   4 use Carp;
  1         2  
  1         82  
13 1     1   605 use Export::Attrs;
  1         10232  
  1         6  
14 1     1   746 use Readonly;
  1         4384  
  1         734  
15              
16             # }}}
17             # {{{ variable declarations
18              
19             my Readonly::Scalar $COPY = 'Copyright (c) PetaMem, s.r.o. 2002-present';
20             our $VERSION = '0.2603300';
21              
22             # }}}
23              
24             # {{{ num2lat_cardinal convert number to text
25              
26             sub num2lat_cardinal :Export {
27 57     57 1 281201 my $positive = shift;
28              
29 57 100 66     852 croak 'You should specify a number from interval [0, 999_999]'
      66        
      100        
30             if !defined $positive
31             || $positive !~ m{\A\d+\z}xms
32             || $positive < 0
33             || $positive > 999_999;
34              
35             # {{{ tokens
36              
37 55         298 my @ones = qw(nulla unus duo tres quattuor quinque sex septem octo novem);
38              
39 55         215 my @teens = qw(
40             decem undecim duodecim tredecim quattuordecim quindecim
41             sedecim septendecim duodeviginti undeviginti
42             );
43              
44 55         222 my @tens = qw(
45             _X _X viginti triginta quadraginta quinquaginta
46             sexaginta septuaginta octoginta nonaginta
47             );
48              
49             # Next-decade words for subtractive forms (8/9 of decade N use decade N+1)
50 55         203 my @next_decade = qw(
51             _X _X triginta quadraginta quinquaginta sexaginta
52             septuaginta octoginta nonaginta centum
53             );
54              
55 55         200 my @hundreds = qw(
56             _X centum ducenti trecenti quadringenti quingenti
57             sescenti septingenti octingenti nongenti
58             );
59              
60             # }}}
61              
62 55 100 66     260 return $ones[$positive] if $positive >= 0 && $positive <= 9;
63 46 100 66     238 return $teens[$positive - 10] if $positive >= 10 && $positive <= 19;
64              
65             # {{{ 20..99
66              
67 38 100 66     167 if ($positive >= 20 && $positive <= 99) {
68 21         67 my $ten_idx = int($positive / 10);
69 21         44 my $unit = $positive % 10;
70              
71 21 100       65 return $tens[$ten_idx] if $unit == 0;
72              
73             # Subtractive: 8 => duode + next decade, 9 => unde + next decade
74             # Exception: 98 is additive (nonaginta octo), but 99 is subtractive (undecentum)
75 18 100 100     73 if ($unit == 8 && $ten_idx != 9) {
76 5         43 return 'duode' . $next_decade[$ten_idx];
77             }
78 13 100       31 if ($unit == 9) {
79 5         40 return 'unde' . $next_decade[$ten_idx];
80             }
81              
82             # Additive: tens + space + unit
83 8         69 return $tens[$ten_idx] . ' ' . $ones[$unit];
84             }
85              
86             # }}}
87             # {{{ 100..999
88              
89 17 100 66     86 if ($positive >= 100 && $positive <= 999) {
90 11         43 my $hun_idx = int($positive / 100);
91 11         23 my $remain = $positive % 100;
92              
93 11         26 my $out = $hundreds[$hun_idx];
94 11 100       43 $out .= ' ' . num2lat_cardinal($remain) if $remain;
95 11         76 return $out;
96             }
97              
98             # }}}
99             # {{{ 1000..999_999
100              
101 6 50 33     27 if ($positive >= 1000 && $positive <= 999_999) {
102 6         20 my $thou_count = int($positive / 1000);
103 6         13 my $remain = $positive % 1000;
104              
105             # mille for 1000, N milia for multiples
106 6         10 my $out;
107 6 100       17 if ($thou_count == 1) {
108 3         7 $out = 'mille';
109             }
110             else {
111 3         9 $out = num2lat_cardinal($thou_count) . ' milia';
112             }
113 6 100       21 $out .= ' ' . num2lat_cardinal($remain) if $remain;
114 6         40 return $out;
115             }
116              
117             # }}}
118              
119 0           return;
120 1     1   11 }
  1         2  
  1         27  
121              
122             # }}}
123              
124             # {{{ num2lat_ordinal convert number to ordinal text
125              
126             sub num2lat_ordinal :Export {
127 0     0 1   my $number = shift;
128              
129 0 0 0       croak 'You should specify a number from interval [1, 999_999]'
      0        
      0        
130             if !defined $number
131             || $number !~ m{\A\d+\z}xms
132             || $number < 1
133             || $number > 999_999;
134              
135             # {{{ tokens
136              
137 0           my @ones_ord = qw(
138             _X primus secundus tertius quartus quintus
139             sextus septimus octavus nonus decimus
140             );
141              
142 0           my @teens_ord = (
143             'undecimus', # 11
144             'duodecimus', # 12
145             'tertius decimus', # 13
146             'quartus decimus', # 14
147             'quintus decimus', # 15
148             'sextus decimus', # 16
149             'septimus decimus', # 17
150             );
151              
152             # Tens ordinals: 20th, 30th, ..., 90th
153 0           my @tens_ord = qw(
154             _X _X vicesimus tricesimus quadragesimus quinquagesimus
155             sexagesimus septuagesimus octogesimus nonagesimus
156             );
157              
158             # Next-decade ordinals for subtractive forms (18/19, 28/29, etc.)
159 0           my @next_decade_ord = qw(
160             _X _X tricesimus quadragesimus quinquagesimus sexagesimus
161             septuagesimus octogesimus nonagesimus centesimus
162             );
163              
164 0           my @hundreds_ord = qw(
165             _X centesimus ducentesimus trecentesimus quadringentesimus
166             quingentesimus sescentesimus septingentesimus octingentesimus
167             nongentesimus
168             );
169              
170             # }}}
171              
172 0 0 0       return $ones_ord[$number] if $number >= 1 && $number <= 10;
173 0 0 0       return $teens_ord[$number - 11] if $number >= 11 && $number <= 17;
174              
175             # {{{ 18..19 — subtractive
176              
177 0 0         return 'duodevicesimus' if $number == 18;
178 0 0         return 'undevicesimus' if $number == 19;
179              
180             # }}}
181             # {{{ 20..99
182              
183 0 0 0       if ($number >= 20 && $number <= 99) {
184 0           my $ten_idx = int($number / 10);
185 0           my $unit = $number % 10;
186              
187 0 0         return $tens_ord[$ten_idx] if $unit == 0;
188              
189             # Subtractive: 8 => duode + next decade ordinal, 9 => unde + next decade ordinal
190 0 0         if ($unit == 8) {
191 0           return 'duode' . $next_decade_ord[$ten_idx];
192             }
193 0 0         if ($unit == 9) {
194 0           return 'unde' . $next_decade_ord[$ten_idx];
195             }
196              
197             # Additive: tens ordinal + unit ordinal
198 0           return $tens_ord[$ten_idx] . ' ' . $ones_ord[$unit];
199             }
200              
201             # }}}
202             # {{{ 100..999
203              
204 0 0 0       if ($number >= 100 && $number <= 999) {
205 0           my $hun_idx = int($number / 100);
206 0           my $remain = $number % 100;
207              
208 0 0         return $hundreds_ord[$hun_idx] if $remain == 0;
209 0           return $hundreds_ord[$hun_idx] . ' ' . num2lat_ordinal($remain);
210             }
211              
212             # }}}
213             # {{{ 1000..999_999
214              
215 0 0 0       if ($number >= 1000 && $number <= 999_999) {
216 0           my $thou_count = int($number / 1000);
217 0           my $remain = $number % 1000;
218              
219 0 0 0       if ($thou_count == 1 && $remain == 0) {
220 0           return 'millesimus';
221             }
222              
223             # For compound thousands: cardinal prefix + millesimus + remainder ordinal
224 0           my $out;
225 0 0         if ($thou_count == 1) {
226 0           $out = 'millesimus';
227             }
228             else {
229 0           $out = num2lat_cardinal($thou_count) . ' millesimus';
230             }
231 0 0         $out .= ' ' . num2lat_ordinal($remain) if $remain;
232 0           return $out;
233             }
234              
235             # }}}
236              
237 0           return;
238 1     1   694 }
  1         8  
  1         4  
239              
240             # }}}
241              
242             # {{{ capabilities declare supported features
243              
244             sub capabilities {
245             return {
246 0     0 1   cardinal => 1,
247             ordinal => 1,
248             };
249             }
250              
251             # }}}
252             1;
253              
254             __END__