line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Lingua::FR::Numbers; |
2
|
6
|
|
|
6
|
|
71752
|
use strict; |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
144
|
|
3
|
|
|
|
|
|
|
|
4
|
6
|
|
|
6
|
|
3110
|
use utf8; # some accents here and there |
|
6
|
|
|
|
|
63
|
|
|
6
|
|
|
|
|
26
|
|
5
|
6
|
|
|
6
|
|
163
|
use Carp qw(carp); |
|
6
|
|
|
|
|
10
|
|
|
6
|
|
|
|
|
262
|
|
6
|
6
|
|
|
6
|
|
22
|
use Exporter; |
|
6
|
|
|
|
|
8
|
|
|
6
|
|
|
|
|
178
|
|
7
|
6
|
|
|
6
|
|
20
|
use vars qw( $VERSION @ISA @EXPORT_OK ); |
|
6
|
|
|
|
|
6
|
|
|
6
|
|
|
|
|
297
|
|
8
|
6
|
|
|
|
|
6574
|
use vars qw( |
9
|
|
|
|
|
|
|
$MODE |
10
|
|
|
|
|
|
|
%NUMBER_NAMES |
11
|
|
|
|
|
|
|
%ORDINALS |
12
|
|
|
|
|
|
|
$OUTPUT_DECIMAL_DELIMITER |
13
|
|
|
|
|
|
|
$SIGN_NAMES |
14
|
6
|
|
|
6
|
|
20
|
); |
|
6
|
|
|
|
|
5
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$VERSION = '1.161910'; |
17
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
18
|
|
|
|
|
|
|
@EXPORT_OK = qw( &number_to_fr &ordinate_to_fr ); |
19
|
|
|
|
|
|
|
$SIGN_NAMES = ('moins'); |
20
|
|
|
|
|
|
|
$OUTPUT_DECIMAL_DELIMITER = ('virgule'); |
21
|
|
|
|
|
|
|
%NUMBER_NAMES = ( |
22
|
|
|
|
|
|
|
0 => 'zéro', |
23
|
|
|
|
|
|
|
1 => 'un', |
24
|
|
|
|
|
|
|
2 => 'deux', |
25
|
|
|
|
|
|
|
3 => 'trois', |
26
|
|
|
|
|
|
|
4 => 'quatre', |
27
|
|
|
|
|
|
|
5 => 'cinq', |
28
|
|
|
|
|
|
|
6 => 'six', |
29
|
|
|
|
|
|
|
7 => 'sept', |
30
|
|
|
|
|
|
|
8 => 'huit', |
31
|
|
|
|
|
|
|
9 => 'neuf', |
32
|
|
|
|
|
|
|
10 => 'dix', |
33
|
|
|
|
|
|
|
11 => 'onze', |
34
|
|
|
|
|
|
|
12 => 'douze', |
35
|
|
|
|
|
|
|
13 => 'treize', |
36
|
|
|
|
|
|
|
14 => 'quatorze', |
37
|
|
|
|
|
|
|
15 => 'quinze', |
38
|
|
|
|
|
|
|
16 => 'seize', |
39
|
|
|
|
|
|
|
17 => 'dix-sept', |
40
|
|
|
|
|
|
|
18 => 'dix-huit', |
41
|
|
|
|
|
|
|
19 => 'dix-neuf', |
42
|
|
|
|
|
|
|
20 => 'vingt', |
43
|
|
|
|
|
|
|
30 => 'trente', |
44
|
|
|
|
|
|
|
40 => 'quarante', |
45
|
|
|
|
|
|
|
50 => 'cinquante', |
46
|
|
|
|
|
|
|
60 => 'soixante', |
47
|
|
|
|
|
|
|
70 => 'soixante', |
48
|
|
|
|
|
|
|
80 => 'quatre-vingt', |
49
|
|
|
|
|
|
|
90 => 'quatre-vingt', |
50
|
|
|
|
|
|
|
100 => 'cent', |
51
|
|
|
|
|
|
|
1e3 => 'mille', |
52
|
|
|
|
|
|
|
1e6 => 'million', |
53
|
|
|
|
|
|
|
1e9 => 'milliard', |
54
|
|
|
|
|
|
|
1e12 => 'billion', # un million de millions |
55
|
|
|
|
|
|
|
1e18 => 'trillion', # un million de billions |
56
|
|
|
|
|
|
|
1e24 => 'quatrillion', # un million de trillions |
57
|
|
|
|
|
|
|
1e30 => 'quintillion', # un million de quatrillions |
58
|
|
|
|
|
|
|
1e36 => 'sextillion', # un million de quintillions, |
59
|
|
|
|
|
|
|
# the sextillion is the biggest legal unit |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
%ORDINALS = ( |
62
|
|
|
|
|
|
|
1 => 'premier', |
63
|
|
|
|
|
|
|
5 => 'cinqu', |
64
|
|
|
|
|
|
|
9 => 'neuv', |
65
|
|
|
|
|
|
|
); |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
sub number_to_fr { |
68
|
209
|
|
|
209
|
1
|
5315
|
my $number = shift; |
69
|
209
|
|
|
|
|
213
|
my @fr_string = (); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# Test if $number is really a number, or return undef, from perldoc |
72
|
|
|
|
|
|
|
# -q numbers |
73
|
209
|
|
|
|
|
440
|
$number =~ s/_//g; # Allow for '_' separating figures |
74
|
209
|
100
|
|
|
|
912
|
if ( $number !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ ) { |
75
|
2
|
|
|
|
|
229
|
carp("Invalid number format: '$number'"); |
76
|
2
|
|
|
|
|
11
|
return; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
207
|
100
|
|
|
|
340
|
if ( $number > ( 1e75 - 1 ) ) { |
80
|
1
|
|
|
|
|
90
|
carp("Number '$number' too big to be represented as string"); |
81
|
1
|
|
|
|
|
5
|
return; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
206
|
100
|
|
|
|
314
|
return $NUMBER_NAMES{0} if $number == 0; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# Add the 'minus' string if the number is negative. |
87
|
204
|
100
|
|
|
|
289
|
push @fr_string, $SIGN_NAMES if abs $number != $number; |
88
|
204
|
|
|
|
|
147
|
$number = abs $number; |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# We deal with decimal numbers by calling number2fr twice, once for |
91
|
|
|
|
|
|
|
# the integer part, and once for the decimal part. |
92
|
204
|
100
|
|
|
|
288
|
if ( $number != int $number ) { |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# XXX Ugly Hack. |
95
|
9
|
|
|
|
|
37
|
( my $decimal ) = $number =~ /\.(\d+)/; |
96
|
|
|
|
|
|
|
|
97
|
9
|
|
|
|
|
20
|
push @fr_string, number_to_fr( int $number ), $OUTPUT_DECIMAL_DELIMITER; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Decimal numbers are correctly interpreted |
100
|
|
|
|
|
|
|
# https://github.com/sebthebert/Lingua-FR-Numbers/commit/89b717da8950d183488c6d93c7d5e638628ef13f |
101
|
9
|
100
|
|
|
|
27
|
if ( $decimal =~ s/^(0+([1-9][0-9]*))$/$2/ ) { |
102
|
5
|
|
|
|
|
6
|
my $decimal_power = 10**length $1; |
103
|
5
|
50
|
|
|
|
7
|
last unless $decimal_power; |
104
|
5
|
|
|
|
|
5
|
my $fr_decimal; |
105
|
5
|
|
|
|
|
6
|
$fr_decimal = number_to_fr($decimal) . ' '; |
106
|
5
|
|
|
|
|
9
|
$fr_decimal .= ordinate_to_fr($decimal_power); |
107
|
5
|
100
|
|
|
|
15
|
$fr_decimal .= 's' if $decimal > 1; |
108
|
5
|
|
|
|
|
6
|
push @fr_string, $fr_decimal; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
else { |
111
|
4
|
|
|
|
|
8
|
push @fr_string, number_to_fr($decimal); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
9
|
|
|
|
|
36
|
return join ( ' ', @fr_string ); |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# First, we split the number by 1000 blocks |
118
|
|
|
|
|
|
|
# i.e: |
119
|
|
|
|
|
|
|
# $block[0] => 0 .. 999 => centaines |
120
|
|
|
|
|
|
|
# $block[1] => 1000 .. 999_999 => milliers |
121
|
|
|
|
|
|
|
# $block[2] => 1e6 .. 999_999_999 => millions |
122
|
|
|
|
|
|
|
# $block[3] => 1e9 .. 1e12-1 => milliards |
123
|
195
|
|
|
|
|
135
|
my @blocks; |
124
|
195
|
|
|
|
|
271
|
while ($number) { |
125
|
440
|
|
|
|
|
381
|
push @blocks, $number % 1000; |
126
|
440
|
|
|
|
|
644
|
$number = int $number / 1000; |
127
|
|
|
|
|
|
|
} |
128
|
195
|
|
|
|
|
160
|
@blocks = reverse @blocks; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# We then go through each block, starting from the greatest |
131
|
|
|
|
|
|
|
# (..., billions, millions, thousands) |
132
|
195
|
|
|
|
|
343
|
foreach ( 0 .. $#blocks ) { |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# No need to spell numbers like 'zero million' |
135
|
440
|
100
|
|
|
|
634
|
next if $blocks[$_] == 0; |
136
|
|
|
|
|
|
|
|
137
|
256
|
|
|
|
|
188
|
my $number = $blocks[$_]; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Determine the 'size' of the block |
140
|
256
|
|
|
|
|
330
|
my $power = 10**( ( $#blocks - $_ ) * 3 ); |
141
|
256
|
|
|
|
|
293
|
my $hundred = int( $blocks[$_] / 100 ); |
142
|
256
|
|
|
|
|
220
|
my $teens = int( $blocks[$_] % 100 / 10 ); |
143
|
256
|
|
|
|
|
189
|
my $units = $blocks[$_] % 10; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Process hundred numbers 'inside' the block |
146
|
|
|
|
|
|
|
# (ie. 235 in 235000 when dealing with thousands.) |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# Hundreds |
149
|
256
|
100
|
|
|
|
321
|
if ($hundred) { |
150
|
86
|
|
|
|
|
117
|
my $fr_hundred; |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# We don't say 'un cent' |
153
|
86
|
100
|
|
|
|
147
|
$fr_hundred = $NUMBER_NAMES{$hundred} . ' ' |
154
|
|
|
|
|
|
|
unless $hundred == 1; |
155
|
|
|
|
|
|
|
|
156
|
86
|
|
|
|
|
82
|
$fr_hundred .= $NUMBER_NAMES{100}; |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Cent prend un 's' quand il est multiplié par un autre |
159
|
|
|
|
|
|
|
# nombre et qu'il termine l'adjectif numéral. |
160
|
86
|
100
|
100
|
|
|
250
|
$fr_hundred .= 's' |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
161
|
|
|
|
|
|
|
if ( $hundred > 1 && !$teens && !$units && $_ == $#blocks ); |
162
|
|
|
|
|
|
|
|
163
|
86
|
|
|
|
|
102
|
push @fr_string, $fr_hundred; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Process number below 100 |
167
|
256
|
|
|
|
|
196
|
my $fr_decimal; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# No tens |
170
|
256
|
100
|
100
|
|
|
1000
|
$fr_decimal = $NUMBER_NAMES{$units} |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
171
|
|
|
|
|
|
|
if ( $units && !$teens ) |
172
|
|
|
|
|
|
|
&& # On ne dit pas 'un mille' (A bit awkward to put here) |
173
|
|
|
|
|
|
|
!( $number == 1 && ( $power == 1000 ) ); |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Cas spécial pour les 80 |
176
|
|
|
|
|
|
|
# On dit 'quatre-vingts' mais 'quatre-vingt-deux' |
177
|
256
|
100
|
100
|
|
|
883
|
if ( $teens == 8 ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
178
|
|
|
|
|
|
|
$fr_decimal = $units |
179
|
|
|
|
|
|
|
? $NUMBER_NAMES{ $teens * 10 } . '-' . $NUMBER_NAMES{$units} |
180
|
18
|
100
|
|
|
|
63
|
: $NUMBER_NAMES{ $teens * 10 } . 's'; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# Cas spécial pour les nombres en 70 et 90 |
184
|
|
|
|
|
|
|
elsif ( $teens == 7 || $teens == 9 ) { |
185
|
50
|
|
|
|
|
43
|
$units += 10; |
186
|
50
|
100
|
100
|
|
|
110
|
if ( $teens == 7 && $units == 11 ) { |
187
|
|
|
|
|
|
|
$fr_decimal = |
188
|
4
|
|
|
|
|
15
|
$NUMBER_NAMES{ $teens * 10 } . ' et ' . $NUMBER_NAMES{$units}; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
else { |
191
|
|
|
|
|
|
|
$fr_decimal = |
192
|
46
|
|
|
|
|
95
|
$NUMBER_NAMES{ $teens * 10 } . '-' . $NUMBER_NAMES{$units}; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Un nombre s'écrit avec un trait d'union sauf s'il est associé |
198
|
|
|
|
|
|
|
# à 'cent' ou à 'mille'; ou s'il est relié par 'et'. |
199
|
|
|
|
|
|
|
# Nombres écrits avec des 'et': 21, 31, 51, 61, 71 |
200
|
|
|
|
|
|
|
elsif ($teens) { |
201
|
79
|
100
|
66
|
|
|
208
|
if ( $teens == 1 ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
202
|
44
|
|
|
|
|
85
|
$fr_decimal = $NUMBER_NAMES{ $teens * 10 + $units }; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
elsif ( $units == 1 || $units == 11 ) { |
205
|
|
|
|
|
|
|
$fr_decimal = |
206
|
5
|
|
|
|
|
19
|
$NUMBER_NAMES{ $teens * 10 } . ' et ' . $NUMBER_NAMES{$units}; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
elsif ( $units == 0 ) { |
209
|
3
|
|
|
|
|
7
|
$fr_decimal = $NUMBER_NAMES{ $teens * 10 }; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
else { |
212
|
|
|
|
|
|
|
$fr_decimal = |
213
|
27
|
|
|
|
|
75
|
$NUMBER_NAMES{ $teens * 10 } . '-' . $NUMBER_NAMES{$units}; |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
256
|
100
|
|
|
|
383
|
push @fr_string, $fr_decimal if $fr_decimal; |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
# Processing thousands, millions, billions, ... |
220
|
256
|
100
|
|
|
|
351
|
if ( $power >= 1e3 ) { |
221
|
122
|
|
|
|
|
69
|
my $fr_power; |
222
|
|
|
|
|
|
|
|
223
|
122
|
100
|
|
|
|
188
|
if ( exists $NUMBER_NAMES{$power} ) { |
224
|
108
|
|
|
|
|
98
|
$fr_power = $NUMBER_NAMES{$power}; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# Billion, milliard, etc. prennent un 's' au pluriel |
227
|
108
|
100
|
100
|
|
|
292
|
$fr_power .= 's' if $number > 1 && $power >= 1e6; |
228
|
|
|
|
|
|
|
|
229
|
108
|
|
|
|
|
112
|
push @fr_string, $fr_power; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# If the power we're looking dealing with doesn't exists |
233
|
|
|
|
|
|
|
# (ie. 1e15, 1e21) we multiply by the lowest power we have, |
234
|
|
|
|
|
|
|
# starting at 1e6. |
235
|
|
|
|
|
|
|
else { |
236
|
14
|
|
|
|
|
11
|
my $sub_power; |
237
|
14
|
|
|
|
|
38
|
my $pow_diff = 1; |
238
|
|
|
|
|
|
|
do { |
239
|
22
|
|
|
|
|
16
|
$pow_diff *= 1_000_000; |
240
|
22
|
|
|
|
|
64
|
$sub_power = $power / $pow_diff; |
241
|
14
|
|
|
|
|
12
|
} until exists $NUMBER_NAMES{$sub_power}; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
# If the power_diff doesn't exists (for really big |
244
|
|
|
|
|
|
|
# numbers), we do the same dance. |
245
|
14
|
50
|
|
|
|
23
|
unless ( exists $NUMBER_NAMES{$pow_diff} ) { |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
} |
248
|
14
|
|
|
|
|
11
|
$fr_power = $NUMBER_NAMES{$pow_diff}; |
249
|
14
|
100
|
|
|
|
28
|
$fr_power .= 's' if $number > 1; |
250
|
14
|
|
|
|
|
33
|
$fr_power .= " de $NUMBER_NAMES{$sub_power}s"; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# XXX Ugly hack - some architecture output "million de billion" instead of "trillion" |
253
|
14
|
|
|
|
|
16
|
$fr_power =~ s/million(s)? de billions?/trillion$1/g; |
254
|
|
|
|
|
|
|
|
255
|
14
|
|
|
|
|
23
|
push @fr_string, $fr_power; |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
256
|
|
|
|
|
319
|
next; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
195
|
|
|
|
|
671
|
return join ( ' ', @fr_string ); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
sub ordinate_to_fr { |
266
|
25
|
|
|
25
|
1
|
3312
|
my $number = shift; |
267
|
|
|
|
|
|
|
|
268
|
25
|
100
|
|
|
|
53
|
unless ( $number > 0 ) { |
269
|
3
|
|
|
|
|
422
|
carp('Ordinates must be strictly positive'); |
270
|
3
|
|
|
|
|
18
|
return; |
271
|
|
|
|
|
|
|
} |
272
|
22
|
100
|
|
|
|
46
|
return $ORDINALS{1} if $number == 1; |
273
|
|
|
|
|
|
|
|
274
|
21
|
|
|
|
|
28
|
my $ordinal = number_to_fr($number); |
275
|
21
|
|
|
|
|
27
|
my $last_digit = $number % 10; |
276
|
|
|
|
|
|
|
|
277
|
21
|
100
|
66
|
|
|
66
|
if ( $last_digit != 1 && exists $ORDINALS{$last_digit} ) { |
278
|
5
|
|
|
|
|
8
|
my $replace = number_to_fr($last_digit); |
279
|
5
|
|
|
|
|
69
|
$ordinal =~ s/$replace$/$ORDINALS{$last_digit}/; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
21
|
|
|
|
|
71
|
$ordinal =~ s/e?$/ième/; |
283
|
21
|
|
|
|
|
26
|
$ordinal =~ s/vingtsième/vingtième/; # Bug #1772 |
284
|
21
|
|
|
|
|
50
|
$ordinal; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# |
288
|
|
|
|
|
|
|
# OO Methods |
289
|
|
|
|
|
|
|
# |
290
|
|
|
|
|
|
|
sub new { |
291
|
76
|
|
|
76
|
1
|
13542
|
my $class = shift; |
292
|
76
|
|
|
|
|
65
|
my $number = shift; |
293
|
76
|
|
|
|
|
140
|
bless \$number, $class; |
294
|
|
|
|
|
|
|
} |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
sub parse { |
297
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
298
|
0
|
0
|
|
|
|
0
|
if ( $_[0] ) { $$self = shift } |
|
0
|
|
|
|
|
0
|
|
299
|
0
|
|
|
|
|
0
|
$self; |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub get_string { |
303
|
76
|
|
|
76
|
1
|
5580
|
my $self = shift; |
304
|
76
|
|
|
|
|
126
|
number_to_fr($$self); |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub get_ordinate { |
308
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
309
|
0
|
|
|
|
|
|
ordinate_to_fr($$self); |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
1; |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
__END__ |