extensobr 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/extensobr.rb CHANGED
@@ -1,461 +1,447 @@
1
- require "extensobr/version"
2
- class Extenso
3
- # Extenso class file
4
-
5
- # Extenso é uma classe que gera a representação por extenso de um número ou valor monetário.
6
- #
7
- # ATENÇÃO: A PÁGINA DE CÓDIGO DESTE ARQUIVO É UTF-8 (Unicode)!
8
- #
9
- # Sua implementação foi feita como prova de conceito, utilizando:
10
- # * Métodos estáticos, implementando o padrão de projeto ("design pattern") SINGLETON;
11
- # * Chamadas recursivas a métodos, minimizando repetições e mantendo o código enxuto; e
12
- # * Tratamento de erros por intermédio de exceções.
13
- #
14
- # = EXEMPLOS DE USO =
15
- #
16
- # Para obter o extenso de um número, utilize Extenso.numero.
17
- #
18
- # puts Extenso.numero(832); # oitocentos e trinta e dois
19
- # puts Extenso.numero(832, Extenso::GENERO_FEM) # oitocentas e trinta e duas
20
- #
21
- #
22
- # Para obter o extenso de um valor monetário, utilize Extenso.moeda.
23
- #
24
- # # IMPORTANTE: veja nota sobre o parâmetro 'valor' na documentação do método!
25
- #
26
- # puts Extenso.moeda(15402) # cento e cinquenta e quatro reais e dois centavos
27
- #
28
- # puts Extenso.moeda(47) # quarenta e sete centavos
29
- #
30
- # puts Extenso.moeda(357082, 2,
31
- # ['peseta', 'pesetas', Extenso::GENERO_FEM],
32
- # ['cêntimo', 'cêntimos', Extenso::GENERO_MASC])
33
- # # três mil, quinhentas e setenta pesetas e oitenta e dois cêntimos
34
- #
35
-
36
- NUM_SING = 0
37
- NUM_PLURAL = 1
38
- POS_GENERO = 2
39
- GENERO_MASC = 0
40
- GENERO_FEM = 1
41
-
42
- VALOR_MAXIMO = 999999999
43
-
44
- # As unidades 1 e 2 variam em gênero, pelo que precisamos de dois conjuntos de strings (masculinas e femininas) para as unidades
45
- UNIDADES = {
46
- GENERO_MASC => {
47
- 1 => 'Um',
48
- 2 => 'Dois',
49
- 3 => 'Três',
50
- 4 => 'Quatro',
51
- 5 => 'Cinco',
52
- 6 => 'Seis',
53
- 7 => 'Sete',
54
- 8 => 'Oito',
55
- 9 => 'Nove'
56
- },
57
- GENERO_FEM => {
58
- 1 => 'Uma',
59
- 2 => 'Duas',
60
- 3 => 'Três',
61
- 4 => 'Quatro',
62
- 5 => 'Cinco',
63
- 6 => 'Seis',
64
- 7 => 'Sete',
65
- 8 => 'Oito',
66
- 9 => 'Nove'
67
- }
68
- }
69
-
70
- DE11A19 = {
71
- 11 => 'Onze',
72
- 12 => 'Doze',
73
- 13 => 'Treze',
74
- 14 => 'Quatorze',
75
- 15 => 'Quinze',
76
- 16 => 'Dezesseis',
77
- 17 => 'Dezessete',
78
- 18 => 'Dezoito',
79
- 19 => 'Dezenove'
80
- }
81
-
82
- DEZENAS = {
83
- 10 => 'Dez',
84
- 20 => 'Vinte',
85
- 30 => 'Trinta',
86
- 40 => 'Quarenta',
87
- 50 => 'Cinquenta',
88
- 60 => 'Sessenta',
89
- 70 => 'Setenta',
90
- 80 => 'Oitenta',
91
- 90 => 'Noventa'
92
- }
93
-
94
- CENTENA_EXATA = 'Cem'
95
-
96
- # As centenas, com exceção de 'cento', também variam em gênero. Aqui também se faz
97
- # necessário dois conjuntos de strings (masculinas e femininas).
98
-
99
- CENTENAS = {
100
- GENERO_MASC => {
101
- 100 => 'Cento',
102
- 200 => 'Duzentos',
103
- 300 => 'Trezentos',
104
- 400 => 'Quatrocentos',
105
- 500 => 'Quinhentos',
106
- 600 => 'Seiscentos',
107
- 700 => 'Setecentos',
108
- 800 => 'Oitocentos',
109
- 900 => 'Novecentos'
110
- },
111
- GENERO_FEM => {
112
- 100 => 'Cento',
113
- 200 => 'Duzentas',
114
- 300 => 'Trezentas',
115
- 400 => 'Quatrocentas',
116
- 500 => 'Quinhentas',
117
- 600 => 'Seiscentas',
118
- 700 => 'Setecentas',
119
- 800 => 'Oitocentas',
120
- 900 => 'Novecentas'
121
- }
122
- }
123
-
124
- #'Mil' é invariável, seja em gênero, seja em número
125
- MILHAR = 'mil'
126
-
127
- MILHOES = {
128
- NUM_SING => 'Milhão',
129
- NUM_PLURAL => 'Milhões'
130
- }
131
-
132
- UNIDADES_ORDINAL = {
133
- GENERO_MASC => {
134
- 1 => 'Primeiro',
135
- 2 => 'Segundo',
136
- 3 => 'Terceiro',
137
- 4 => 'Quarto',
138
- 5 => 'Quinto',
139
- 6 => 'Sexto',
140
- 7 => 'Sétimo',
141
- 8 => 'Oitavo',
142
- 9 => 'Nono'},
143
- GENERO_FEM => {
144
- 1 => 'Primeira',
145
- 2 => 'Segunda',
146
- 3 => 'Terceira',
147
- 4 => 'Quarta',
148
- 5 => 'Quinta',
149
- 6 => 'Sexta',
150
- 7 => 'Sétima',
151
- 8 => 'Oitava',
152
- 9 => 'Nona'}}
153
-
154
- DEZENAS_ORDINAL = {
155
- GENERO_MASC => {
156
- 10 => 'Décimo',
157
- 20 => 'Vigésimo',
158
- 30 => 'Trigésimo',
159
- 40 => 'Quadragésimo',
160
- 50 => 'Quinquagésimo',
161
- 60 => 'Sexagésimo',
162
- 70 => 'Septuagésimo',
163
- 80 => 'Octogésimo',
164
- 90 => 'Nonagésimo'},
165
- GENERO_FEM => {
166
- 10 => 'Décima',
167
- 20 => 'Vigésima',
168
- 30 => 'Trigésima',
169
- 40 => 'Quadragésima',
170
- 50 => 'Quinquagésima',
171
- 60 => 'Sexagésima',
172
- 70 => 'Septuagésima',
173
- 80 => 'Octogésima',
174
- 90 => 'Nonagésima'}}
175
-
176
- CENTENAS_ORDINAL = {
177
- GENERO_MASC => {
178
- 100 => 'Centésimo',
179
- 200 => 'Ducentésimo',
180
- 300 => 'Trecentésimo',
181
- 400 => 'Quadringentésimo',
182
- 500 => 'Quingentésimo',
183
- 600 => 'Seiscentésimo',
184
- 700 => 'Septingentésimo',
185
- 800 => 'Octingentésimo',
186
- 900 => 'Noningentésimo'},
187
- GENERO_FEM => {
188
- 100 => 'Centésima',
189
- 200 => 'Ducentésima',
190
- 300 => 'Trecentésima',
191
- 400 => 'Quadringentésima',
192
- 500 => 'Quingentésima',
193
- 600 => 'Seiscentésima',
194
- 700 => 'Septingentésima',
195
- 800 => 'Octingentésima',
196
- 900 => 'Noningentésima'}}
197
-
198
-
199
- MILHAR_ORDINAL = {
200
- GENERO_MASC => {
201
- 1000 => 'Milésimo'},
202
- GENERO_FEM =>{
203
- 1000 => 'Milésima'}}
204
-
205
- def self.is_int(s)
206
- Integer(s) != nil rescue false
207
- end
208
-
209
- #######################################################################################################################################
210
-
211
- def self.numero (valor, genero = GENERO_MASC)
212
-
213
- # Gera a representação por extenso de um número inteiro, maior que zero e menor ou igual a VALOR_MAXIMO.
214
- #
215
- # PARÂMETROS:
216
- # valor (Integer) O valor numérico cujo extenso se deseja gerar
217
- #
218
- # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
219
- # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
220
- #
221
- # VALOR DE RETORNO:
222
- # (String) O número por extenso
223
-
224
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
225
-
226
- if !is_int(valor)
227
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
228
- elsif valor <= 0
229
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' igual a ou menor que zero (recebido: '#{valor}')"
230
- elsif valor > VALOR_MAXIMO
231
- raise '[Exceção em Extenso::numero] Parâmetro ''valor'' deve ser um inteiro entre 1 e ' + VALOR_MAXIMO.to_s + " (recebido: '#{valor}')"
232
- elsif genero != GENERO_MASC && genero != GENERO_FEM
233
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
234
-
235
- # ------------------------------------------------
236
-
237
- elsif valor >= 1 && valor <= 9
238
- UNIDADES[genero][valor]
239
-
240
- elsif valor == 10
241
- DEZENAS[valor]
242
-
243
- elsif valor >= 11 && valor <= 19
244
- DE11A19[valor]
245
-
246
- elsif valor >= 20 && valor <= 99
247
- dezena = valor - (valor % 10)
248
- ret = DEZENAS[dezena]
249
- # Chamada recursiva à função para processar resto se este for maior que zero.
250
- # O conectivo 'e' é utilizado entre dezenas e unidades.
251
- resto = valor - dezena
252
- if resto > 0
253
- ret += ' e ' + self.numero(resto, genero)
254
- end
255
- ret
256
-
257
- elsif valor == 100
258
- CENTENA_EXATA
259
-
260
- elsif valor >= 101 && valor <= 999
261
- centena = valor - (valor % 100)
262
- ret = CENTENAS[genero][centena] # As centenas (exceto 'cento') variam em gênero
263
- # Chamada recursiva à função para processar resto se este for maior que zero.
264
- # O conectivo 'e' é utilizado entre centenas e dezenas.
265
- resto = valor - centena
266
- if resto > 0
267
- ret += ' e ' + self.numero(resto, genero)
268
- end
269
- ret
270
-
271
- elsif valor >= 1000 && valor <= 999999
272
- # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000,
273
- # assim determinando a quantidade de milhares. O resultado é enviado a uma chamada recursiva
274
- # da função. A palavra 'mil' não se flexiona.
275
- milhar = (valor / 1000).floor
276
- ret = self.numero(milhar, GENERO_MASC) + ' ' + MILHAR # 'Mil' é do gênero masculino
277
- resto = valor % 1000
278
- # Chamada recursiva à função para processar resto se este for maior que zero.
279
- # O conectivo 'e' é utilizado entre milhares e números entre 1 e 99, bem como antes de centenas exatas.
280
- if resto > 0 && ((resto >= 1 && resto <= 99) || resto % 100 == 0)
281
- ret += ' e ' + self.numero(resto, genero)
282
- # Nos demais casos, após o milhar é utilizada a vírgula.
283
- elsif (resto > 0)
284
- ret += ', ' + self.numero(resto, genero)
285
- end
286
- ret
287
-
288
- elsif valor >= 100000 && valor <= VALOR_MAXIMO
289
- # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000000,
290
- # assim determinando a quantidade de milhões. O resultado é enviado a uma chamada recursiva
291
- # da função. A palavra 'milhão' flexiona-se no plural.
292
- milhoes = (valor / 1000000).floor
293
- ret = self.numero(milhoes, GENERO_MASC) + ' ' # Milhão e milhões são do gênero masculino
294
-
295
- # Se a o número de milhões for maior que 1, deve-se utilizar a forma flexionada no plural
296
- ret += milhoes == 1 ? MILHOES[NUM_SING] : MILHOES[NUM_PLURAL]
297
-
298
- resto = valor % 1000000
299
-
300
- # Chamada recursiva à função para processar resto se este for maior que zero.
301
- # O conectivo 'e' é utilizado entre milhões e números entre 1 e 99, bem como antes de centenas exatas.
302
- if resto && (resto >= 1 && resto <= 99)
303
- ret += ' e ' + self.numero(resto, genero)
304
- # Nos demais casos, após o milhão é utilizada a vírgula.
305
- elsif resto > 0
306
- ret += ', ' + self.numero(resto, genero)
307
- end
308
- ret
309
-
310
- end
311
-
312
- end
313
-
314
- #######################################################################################################################################
315
-
316
- def self.moeda(
317
- valor,
318
- casas_decimais = 2,
319
- info_unidade = ['Real', 'Reais', GENERO_MASC],
320
- info_fracao = ['Centavo', 'Centavos', GENERO_MASC]
321
- )
322
-
323
- # Gera a representação por extenso de um valor monetário, maior que zero e menor ou igual a Extenso::VALOR_MAXIMO.
324
- #
325
- #
326
- # PARÂMETROS:
327
- # valor (Integer) O valor monetário cujo extenso se deseja gerar.
328
- # ATENÇÃO: PARA EVITAR OS CONHECIDOS PROBLEMAS DE ARREDONDAMENTO COM NÚMEROS DE PONTO FLUTUANTE, O VALOR DEVE SER PASSADO
329
- # JÁ DEVIDAMENTE MULTIPLICADO POR 10 ELEVADO A $casasDecimais (o que equivale, normalmente, a passar o valor com centavos
330
- # multiplicado por 100)
331
- #
332
- # casas_decimais (Integer) [Opcional; valor padrão: 2] Número de casas decimais a serem consideradas como parte fracionária (centavos)
333
- #
334
- # info_unidade (Array) [Opcional; valor padrão: ['real', 'reais', Extenso::GENERO_MASC]] Fornece informações sobre a moeda a ser
335
- # utilizada. O primeiro valor da matriz corresponde ao nome da moeda no singular, o segundo ao nome da moeda no plural e o terceiro
336
- # ao gênero gramatical do nome da moeda (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
337
- #
338
- # info_fracao (Array) [Opcional; valor padrão: ['centavo', 'centavos', Extenso::GENERO_MASC]] Provê informações sobre a parte fracionária
339
- # da moeda. O primeiro valor da matriz corresponde ao nome da parte fracionária no singular, o segundo ao nome da parte fracionária no plural
340
- # e o terceiro ao gênero gramatical da parte fracionária (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
341
- #
342
- # VALOR DE RETORNO:
343
- # (String) O valor monetário por extenso
344
-
345
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
346
-
347
- if ! self.is_int(valor)
348
- raise "[Exceção em Extenso.moeda] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
349
-
350
- elsif valor <= 0
351
- raise "[Exceção em Extenso.moeda] Parâmetro valor igual a ou menor que zero (recebido: '#{valor}')"
352
-
353
- elsif ! self.is_int(casas_decimais) || casas_decimais < 0
354
- raise "[Exceção em Extenso.moeda] Parâmetro 'casas_decimais' não é numérico ou é menor que zero (recebido: '#{casas_decimais}')"
355
-
356
- elsif info_unidade.class != Array || info_unidade.length < 3
357
- temp = info_unidade.class == Array ? '[' + info_unidade.join(', ') + ']' : "'#{info_unidade}'"
358
- raise "[Exceção em Extenso.moeda] Parâmetro 'info_unidade' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
359
-
360
- elsif info_unidade[POS_GENERO] != GENERO_MASC && info_unidade[POS_GENERO] != GENERO_FEM
361
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'info_unidade[POS_GENERO]' (recebido: '#{info_unidade[POS_GENERO]}')"
362
-
363
- elsif info_fracao.class != Array || info_fracao.length < 3
364
- temp = info_fracao.class == Array ? '[' + info_fracao.join(', ') + ']' : "'#{info_fracao}'"
365
- raise "[Exceção em Extenso.moeda] Parâmetro 'info_fracao' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
366
-
367
- elsif info_fracao[POS_GENERO] != GENERO_MASC && info_fracao[POS_GENERO] != GENERO_FEM
368
- raise "[Exceção em Extenso.moeda] valor incorreto para o parâmetro 'info_fracao[POS_GENERO]' (recebido: '#{info_fracao[POS_GENERO]}')."
369
-
370
- end
371
-
372
- # -----------------------------------------------
373
-
374
- ret = ''
375
-
376
- # A parte inteira do valor monetário corresponde ao valor passado dividido por 10 elevado a casas_decimais, desprezado o resto.
377
- # Assim, com o padrão de 2 casas_decimais, o valor será dividido por 100 (10^2), e o resto é descartado utilizando-se floor().
378
- parte_inteira = valor.floor / (10**casas_decimais)
379
-
380
- # A parte fracionária ('centavos'), por seu turno, corresponderá ao resto da divisão do valor por 10 elevado a casas_decimais.
381
- # No cenário comum em que trabalhamos com 2 casas_decimais, será o resto da divisão do valor por 100 (10^2).
382
- fracao = valor % (10**casas_decimais)
383
-
384
- # O extenso para a parte_inteira somente será gerado se esta for maior que zero. Para tanto, utilizamos
385
- # os préstimos do método Extenso::numero().
386
- if parte_inteira > 0
387
- ret = self.numero(parte_inteira, info_unidade[POS_GENERO]) + ' de '
388
- ret += parte_inteira == 1 ? info_unidade[NUM_SING] : info_unidade[NUM_PLURAL]
389
- ret
390
- end
391
-
392
- # De forma semelhante, o extenso da fracao somente será gerado se esta for maior que zero. */
393
- if fracao > 0
394
- # Se a parte_inteira for maior que zero, o extenso para ela já terá sido gerado. Antes de juntar os
395
- # centavos, precisamos colocar o conectivo 'e'.
396
- if parte_inteira > 0
397
- ret += ' e '
398
- end
399
- ret += self.numero(fracao, info_fracao[POS_GENERO]) + ' '
400
- ret += parte_inteira == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
401
- end
402
-
403
- ret
404
-
405
- end
406
-
407
- ######################################################################################################################################################
408
- def self.ordinal (valor, genero = GENERO_MASC)
409
-
410
- # Gera a representação ordinal de um número inteiro de 1 à 1000
411
-
412
- # PARÂMETROS:
413
- # valor (Integer) O valor numérico cujo extenso se deseja gerar
414
- #
415
- # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
416
- # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
417
- #
418
- # VALOR DE RETORNO:
419
- # (String) O número por extenso
420
-
421
- # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
422
-
423
- if !is_int(valor)
424
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
425
- elsif valor <= 0
426
- raise "[Exceção em Extenso.numero] Parâmetro 'valor' igual a ou menor que zero (recebido: '#{valor}')"
427
- elsif valor > VALOR_MAXIMO
428
- raise '[Exceção em Extenso::numero] Parâmetro ''valor'' deve ser um inteiro entre 1 e ' + VALOR_MAXIMO.to_s + " (recebido: '#{valor}')"
429
- elsif genero != GENERO_MASC && genero != GENERO_FEM
430
- raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
431
- # ------------------------------------------------
432
- elsif valor >= 1 && valor <= 9
433
- return UNIDADES_ORDINAL[genero][valor]
434
- elsif valor >= 10 && valor <= 99
435
- dezena = valor - (valor % 10)
436
- resto = valor - dezena
437
- ret = DEZENAS_ORDINAL[genero][dezena]+" "
438
- if resto > 0 then ret+= self.ordinal(resto,genero); end
439
- return ret
440
- elsif valor >= 100 && valor <= 999
441
- centena = valor - (valor % 100)
442
- resto = valor - centena
443
- ret = CENTENAS_ORDINAL[genero][centena]+" "
444
- if resto > 0 then ret += self.ordinal(resto, genero); end
445
- return ret
446
- elsif valor == 1000
447
- return MILHAR_ORDINAL[genero][valor]+" "
448
- end
449
- end
450
-
451
- # Gera o valor em formato de Real
452
- #
453
- # Exemplo:
454
- # Extenso.real_formatado(10) - R$ 10,00
455
- # Extenso.real_formatado(1.55) - R$ 1,55
456
- #
457
- # @params[Object]
458
- def self.real_formatado(valor)
459
- "R$ #{format("%.2f", valor).to_s.gsub('.', ',')}"
460
- end
1
+ require "extensobr/version"
2
+ class Extenso
3
+
4
+ BRL = {:delimiter => ".", :separator => ",", :unit => "R$", :precision => 2, :position => "before"}
5
+
6
+ NUM_SING = 0
7
+ NUM_PLURAL = 1
8
+ POS_GENERO = 2
9
+ GENERO_MASC = 0
10
+ GENERO_FEM = 1
11
+
12
+ VALOR_MAXIMO = 999999999
13
+
14
+ # As unidades 1 e 2 variam em gênero, pelo que precisamos de dois conjuntos de strings (masculinas e femininas) para as unidades
15
+ UNIDADES = {
16
+ GENERO_MASC => {
17
+ 1 => 'Um',
18
+ 2 => 'Dois',
19
+ 3 => 'Três',
20
+ 4 => 'Quatro',
21
+ 5 => 'Cinco',
22
+ 6 => 'Seis',
23
+ 7 => 'Sete',
24
+ 8 => 'Oito',
25
+ 9 => 'Nove'
26
+ },
27
+ GENERO_FEM => {
28
+ 1 => 'Uma',
29
+ 2 => 'Duas',
30
+ 3 => 'Três',
31
+ 4 => 'Quatro',
32
+ 5 => 'Cinco',
33
+ 6 => 'Seis',
34
+ 7 => 'Sete',
35
+ 8 => 'Oito',
36
+ 9 => 'Nove'
37
+ }
38
+ }
39
+
40
+ DE11A19 = {
41
+ 11 => 'Onze',
42
+ 12 => 'Doze',
43
+ 13 => 'Treze',
44
+ 14 => 'Quatorze',
45
+ 15 => 'Quinze',
46
+ 16 => 'Dezesseis',
47
+ 17 => 'Dezessete',
48
+ 18 => 'Dezoito',
49
+ 19 => 'Dezenove'
50
+ }
51
+
52
+ DEZENAS = {
53
+ 10 => 'Dez',
54
+ 20 => 'Vinte',
55
+ 30 => 'Trinta',
56
+ 40 => 'Quarenta',
57
+ 50 => 'Cinquenta',
58
+ 60 => 'Sessenta',
59
+ 70 => 'Setenta',
60
+ 80 => 'Oitenta',
61
+ 90 => 'Noventa'
62
+ }
63
+
64
+ CENTENA_EXATA = 'Cem'
65
+
66
+ # As centenas, com exceção de 'cento', também variam em gênero. Aqui também se faz
67
+ # necessário dois conjuntos de strings (masculinas e femininas).
68
+
69
+ CENTENAS = {
70
+ GENERO_MASC => {
71
+ 100 => 'Cento',
72
+ 200 => 'Duzentos',
73
+ 300 => 'Trezentos',
74
+ 400 => 'Quatrocentos',
75
+ 500 => 'Quinhentos',
76
+ 600 => 'Seiscentos',
77
+ 700 => 'Setecentos',
78
+ 800 => 'Oitocentos',
79
+ 900 => 'Novecentos'
80
+ },
81
+ GENERO_FEM => {
82
+ 100 => 'Cento',
83
+ 200 => 'Duzentas',
84
+ 300 => 'Trezentas',
85
+ 400 => 'Quatrocentas',
86
+ 500 => 'Quinhentas',
87
+ 600 => 'Seiscentas',
88
+ 700 => 'Setecentas',
89
+ 800 => 'Oitocentas',
90
+ 900 => 'Novecentas'
91
+ }
92
+ }
93
+
94
+ #'Mil' é invariável, seja em gênero, seja em número
95
+ MILHAR = 'mil'
96
+
97
+ MILHOES = {
98
+ NUM_SING => 'milhão',
99
+ NUM_PLURAL => 'milhões'
100
+ }
101
+
102
+ UNIDADES_ORDINAL = {
103
+ GENERO_MASC => {
104
+ 1 => 'Primeiro',
105
+ 2 => 'Segundo',
106
+ 3 => 'Terceiro',
107
+ 4 => 'Quarto',
108
+ 5 => 'Quinto',
109
+ 6 => 'Sexto',
110
+ 7 => 'Sétimo',
111
+ 8 => 'Oitavo',
112
+ 9 => 'Nono'},
113
+ GENERO_FEM => {
114
+ 1 => 'Primeira',
115
+ 2 => 'Segunda',
116
+ 3 => 'Terceira',
117
+ 4 => 'Quarta',
118
+ 5 => 'Quinta',
119
+ 6 => 'Sexta',
120
+ 7 => 'Sétima',
121
+ 8 => 'Oitava',
122
+ 9 => 'Nona'}}
123
+
124
+ DEZENAS_ORDINAL = {
125
+ GENERO_MASC => {
126
+ 10 => 'Décimo',
127
+ 20 => 'Vigésimo',
128
+ 30 => 'Trigésimo',
129
+ 40 => 'Quadragésimo',
130
+ 50 => 'Quinquagésimo',
131
+ 60 => 'Sexagésimo',
132
+ 70 => 'Septuagésimo',
133
+ 80 => 'Octogésimo',
134
+ 90 => 'Nonagésimo'},
135
+ GENERO_FEM => {
136
+ 10 => 'Décima',
137
+ 20 => 'Vigésima',
138
+ 30 => 'Trigésima',
139
+ 40 => 'Quadragésima',
140
+ 50 => 'Quinquagésima',
141
+ 60 => 'Sexagésima',
142
+ 70 => 'Septuagésima',
143
+ 80 => 'Octogésima',
144
+ 90 => 'Nonagésima'}}
145
+
146
+ CENTENAS_ORDINAL = {
147
+ GENERO_MASC => {
148
+ 100 => 'Centésimo',
149
+ 200 => 'Ducentésimo',
150
+ 300 => 'Trecentésimo',
151
+ 400 => 'Quadringentésimo',
152
+ 500 => 'Quingentésimo',
153
+ 600 => 'Seiscentésimo',
154
+ 700 => 'Septingentésimo',
155
+ 800 => 'Octingentésimo',
156
+ 900 => 'Noningentésimo'},
157
+ GENERO_FEM => {
158
+ 100 => 'Centésima',
159
+ 200 => 'Ducentésima',
160
+ 300 => 'Trecentésima',
161
+ 400 => 'Quadringentésima',
162
+ 500 => 'Quingentésima',
163
+ 600 => 'Seiscentésima',
164
+ 700 => 'Septingentésima',
165
+ 800 => 'Octingentésima',
166
+ 900 => 'Noningentésima'}}
167
+
168
+
169
+ MILHAR_ORDINAL = {
170
+ GENERO_MASC => {
171
+ 1000 => 'Milésimo'},
172
+ GENERO_FEM =>{
173
+ 1000 => 'Milésima'}}
174
+
175
+ def self.is_int(s)
176
+ Integer(s) != nil rescue false
177
+ end
178
+
179
+ def self.is_float?(str)
180
+ !!Float(str) rescue false
181
+ end
182
+
183
+ #######################################################################################################################################
184
+
185
+ def self.numero (valor, genero = GENERO_MASC)
186
+
187
+ # Gera a representação por extenso de um número inteiro, maior que zero e menor ou igual a VALOR_MAXIMO.
188
+ #
189
+ # PARÂMETROS:
190
+ # valor (Integer) O valor numérico cujo extenso se deseja gerar
191
+ #
192
+ # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
193
+ # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
194
+ #
195
+ # VALOR DE RETORNO:
196
+ # (String) O número por extenso
197
+
198
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
199
+
200
+ if !is_int(valor)
201
+ raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
202
+ elsif valor <= 0
203
+ 'Zero'
204
+ elsif valor > VALOR_MAXIMO
205
+ raise "[Exceção em Extenso.numero] Parâmetro '#{valor} deve ser um inteiro entre 1 e #{VALOR_MAXIMO.to_s} (recebido: '#{valor}')"
206
+ elsif genero != GENERO_MASC && genero != GENERO_FEM
207
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
208
+
209
+ # ------------------------------------------------
210
+
211
+ elsif valor >= 1 && valor <= 9
212
+ UNIDADES[genero][valor]
213
+
214
+ elsif valor == 10
215
+ DEZENAS[valor]
216
+
217
+ elsif valor >= 11 && valor <= 19
218
+ DE11A19[valor]
219
+
220
+ elsif valor >= 20 && valor <= 99
221
+ dezena = valor - (valor % 10)
222
+ ret = DEZENAS[dezena]
223
+ # Chamada recursiva à função para processar resto se este for maior que zero.
224
+ # O conectivo 'e' é utilizado entre dezenas e unidades.
225
+ resto = valor - dezena
226
+ if resto > 0
227
+ ret += ' e ' + self.numero(resto, genero)
228
+ end
229
+ ret
230
+
231
+ elsif valor == 100
232
+ CENTENA_EXATA
233
+
234
+ elsif valor >= 101 && valor <= 999
235
+ centena = valor - (valor % 100)
236
+ ret = CENTENAS[genero][centena] # As centenas (exceto 'cento') variam em gênero
237
+ # Chamada recursiva à função para processar resto se este for maior que zero.
238
+ # O conectivo 'e' é utilizado entre centenas e dezenas.
239
+ resto = valor - centena
240
+ if resto > 0
241
+ ret += ' e ' + self.numero(resto, genero)
242
+ end
243
+ ret
244
+
245
+ elsif valor >= 1000 && valor <= 999999
246
+ # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000,
247
+ # assim determinando a quantidade de milhares. O resultado é enviado a uma chamada recursiva
248
+ # da função. A palavra 'mil' não se flexiona.
249
+ milhar = (valor / 1000).floor
250
+ ret = self.numero(milhar, GENERO_MASC) + ' ' + MILHAR # 'Mil' é do gênero masculino
251
+ resto = valor % 1000
252
+ # Chamada recursiva à função para processar resto se este for maior que zero.
253
+ # O conectivo 'e' é utilizado entre milhares e números entre 1 e 99, bem como antes de centenas exatas.
254
+ if resto > 0 && ((resto >= 1 && resto <= 99) || resto % 100 == 0)
255
+ ret += ' e ' + self.numero(resto, genero)
256
+ # Nos demais casos, após o milhar é utilizada a vírgula.
257
+ elsif (resto > 0)
258
+ ret += ', ' + self.numero(resto, genero)
259
+ end
260
+ ret
261
+
262
+ elsif valor >= 100000 && valor <= VALOR_MAXIMO
263
+ # A função 'floor' é utilizada para encontrar o inteiro da divisão de valor por 1000000,
264
+ # assim determinando a quantidade de milhões. O resultado é enviado a uma chamada recursiva
265
+ # da função. A palavra 'milhão' flexiona-se no plural.
266
+ milhoes = (valor / 1000000).floor
267
+ ret = self.numero(milhoes, GENERO_MASC) + ' ' # Milhão e milhões são do gênero masculino
268
+
269
+ # Se a o número de milhões for maior que 1, deve-se utilizar a forma flexionada no plural
270
+ ret += milhoes == 1 ? MILHOES[NUM_SING] : MILHOES[NUM_PLURAL]
271
+
272
+ resto = valor % 1000000
273
+
274
+ # Chamada recursiva à função para processar resto se este for maior que zero.
275
+ # O conectivo 'e' é utilizado entre milhões e números entre 1 e 99, bem como antes de centenas exatas.
276
+ if resto && (resto >= 1 && resto <= 99)
277
+ ret += ' e ' + self.numero(resto, genero)
278
+ # Nos demais casos, após o milhão é utilizada a vírgula.
279
+ elsif resto > 0
280
+ ret += ', ' + self.numero(resto, genero)
281
+ end
282
+ ret
283
+
284
+ end
285
+
286
+ end
287
+
288
+ #######################################################################################################################################
289
+
290
+ def self.moeda(
291
+ valor,
292
+ casas_decimais = 2,
293
+ info_unidade = ['Real', 'Reais', GENERO_MASC],
294
+ info_fracao = ['Centavo', 'Centavos', GENERO_MASC]
295
+ )
296
+
297
+ # Gera a representação por extenso de um valor monetário, maior que zero e menor ou igual a Extenso::VALOR_MAXIMO.
298
+ #
299
+ #
300
+ # PARÂMETROS:
301
+ # valor (Float) O valor monetário cujo extenso se deseja gerar.
302
+ # casas_decimais (Integer) [Opcional; valor padrão: 2] Número de casas decimais a serem consideradas como parte fracionária (centavos)
303
+ #
304
+ # info_unidade (Array) [Opcional; valor padrão: ['real', 'reais', Extenso::GENERO_MASC]] Fornece informações sobre a moeda a ser
305
+ # utilizada. O primeiro valor da matriz corresponde ao nome da moeda no singular, o segundo ao nome da moeda no plural e o terceiro
306
+ # ao gênero gramatical do nome da moeda (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
307
+ #
308
+ # info_fracao (Array) [Opcional; valor padrão: ['centavo', 'centavos', Extenso::GENERO_MASC]] Provê informações sobre a parte fracionária
309
+ # da moeda. O primeiro valor da matriz corresponde ao nome da parte fracionária no singular, o segundo ao nome da parte fracionária no plural
310
+ # e o terceiro ao gênero gramatical da parte fracionária (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
311
+ #
312
+ # VALOR DE RETORNO:
313
+ # (String) O valor monetário por extenso
314
+
315
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
316
+
317
+ if ! self.is_float?(valor.to_f.round(casas_decimais).to_s)
318
+ raise "[Exceção em Extenso.moeda] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
319
+
320
+ elsif valor <= 0
321
+ "Zero"
322
+
323
+ elsif ! self.is_int(casas_decimais) || casas_decimais < 0
324
+ raise "[Exceção em Extenso.moeda] Parâmetro 'casas_decimais' não é numérico ou é menor que zero (recebido: '#{casas_decimais}')"
325
+
326
+ elsif info_unidade.class != Array || info_unidade.length < 3
327
+ temp = info_unidade.class == Array ? '[' + info_unidade.join(', ') + ']' : "'#{info_unidade}'"
328
+ raise "[Exceção em Extenso.moeda] Parâmetro 'info_unidade' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
329
+
330
+ elsif info_unidade[POS_GENERO] != GENERO_MASC && info_unidade[POS_GENERO] != GENERO_FEM
331
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'info_unidade[POS_GENERO]' (recebido: '#{info_unidade[POS_GENERO]}')"
332
+
333
+ elsif info_fracao.class != Array || info_fracao.length < 3
334
+ temp = info_fracao.class == Array ? '[' + info_fracao.join(', ') + ']' : "'#{info_fracao}'"
335
+ raise "[Exceção em Extenso.moeda] Parâmetro 'info_fracao' não é uma matriz com 3 (três) elementos (recebido: #{temp})"
336
+
337
+ elsif info_fracao[POS_GENERO] != GENERO_MASC && info_fracao[POS_GENERO] != GENERO_FEM
338
+ raise "[Exceção em Extenso.moeda] valor incorreto para o parâmetro 'info_fracao[POS_GENERO]' (recebido: '#{info_fracao[POS_GENERO]}')."
339
+
340
+ end
341
+
342
+ # -----------------------------------------------
343
+
344
+ ret = ''
345
+
346
+ valor = sprintf("%#{casas_decimais.to_f / 100}f", valor)
347
+ # A parte inteira do valor monetário corresponde ao valor passado antes do '.' no tipo float.
348
+ parte_inteira = valor.split('.')[0].to_i
349
+
350
+ # A parte fracionária ('centavos'), por seu turno, corresponderá ao valor passado depois do '.'
351
+ fracao = valor.to_s.split('.')[1].to_i
352
+
353
+ # os préstimos do método Extenso::numero().
354
+ if parte_inteira > 0
355
+ ret = self.numero(parte_inteira, info_unidade[POS_GENERO]) + ((parte_inteira >= 1000000 && (parte_inteira.to_s.chars.reverse[5] == "0" ) ) ? ' de ' : ' ')
356
+ ret += parte_inteira == 1 ? info_unidade[NUM_SING] : info_unidade[NUM_PLURAL]
357
+ ret
358
+ end
359
+
360
+ # De forma semelhante, o extenso da fracao somente será gerado se esta for maior que zero. */
361
+ if fracao > 0
362
+ # Se a parte_inteira for maior que zero, o extenso para ela já terá sido gerado. Antes de juntar os
363
+ # centavos, precisamos colocar o conectivo 'e'.
364
+ if parte_inteira > 0
365
+ ret += ' e '
366
+ end
367
+ ret += self.numero(fracao, info_fracao[POS_GENERO]) + ' '
368
+ ret += fracao == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
369
+ end
370
+
371
+ if valor.to_f == 0
372
+ ret += self.numero(fracao, info_fracao[POS_GENERO]) + ' '
373
+ ret += parte_inteira == 1 ? info_fracao[NUM_SING] : info_fracao[NUM_PLURAL]
374
+ end
375
+
376
+ ret
377
+
378
+ end
379
+
380
+ ######################################################################################################################################################
381
+ def self.ordinal (valor, genero = GENERO_MASC)
382
+
383
+ # Gera a representação ordinal de um número inteiro de 1 à 1000
384
+
385
+ # PARÂMETROS:
386
+ # valor (Integer) O valor numérico cujo extenso se deseja gerar
387
+ #
388
+ # genero (Integer) [Opcional; valor padrão: Extenso::GENERO_MASC] O gênero gramatical (Extenso::GENERO_MASC ou Extenso::GENERO_FEM)
389
+ # do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
390
+ #
391
+ # VALOR DE RETORNO:
392
+ # (String) O número por extenso
393
+
394
+ # ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ----
395
+
396
+ if !is_int(valor)
397
+ raise "[Exceção em Extenso.numero] Parâmetro 'valor' não é numérico (recebido: '#{valor}')"
398
+ elsif valor <= 0
399
+ 'Zero'
400
+ # raise "[Exceção em Extenso.numero] Parâmetro 'valor' igual a ou menor que zero (recebido: '#{valor}')"
401
+ elsif valor > VALOR_MAXIMO
402
+ raise '[Exceção em Extenso::numero] Parâmetro ''valor'' deve ser um inteiro entre 1 e ' + VALOR_MAXIMO.to_s + " (recebido: '#{valor}')"
403
+ elsif genero != GENERO_MASC && genero != GENERO_FEM
404
+ raise "Exceção em Extenso: valor incorreto para o parâmetro 'genero' (recebido: '#{genero}')"
405
+ # ------------------------------------------------
406
+ elsif valor >= 1 && valor <= 9
407
+ return UNIDADES_ORDINAL[genero][valor]
408
+ elsif valor >= 10 && valor <= 99
409
+ dezena = valor - (valor % 10)
410
+ resto = valor - dezena
411
+ ret = DEZENAS_ORDINAL[genero][dezena]+" "
412
+ if resto > 0 then ret+= self.ordinal(resto,genero); end
413
+ return ret
414
+ elsif valor >= 100 && valor <= 999
415
+ centena = valor - (valor % 100)
416
+ resto = valor - centena
417
+ ret = CENTENAS_ORDINAL[genero][centena]+" "
418
+ if resto > 0 then ret += self.ordinal(resto, genero); end
419
+ return ret
420
+ elsif valor == 1000
421
+ return MILHAR_ORDINAL[genero][valor]+" "
422
+ end
423
+ end
424
+
425
+ # Gera o valor em formato de Real
426
+ #
427
+ # Exemplo:
428
+ # Extenso.real_formatado(10) - R$ 10,00
429
+ # Extenso.real_formatado(1.55) - R$ 1,55
430
+ #
431
+ # @params[Object]
432
+ def self.real_formatado(valor)
433
+ float_valor = sprintf("%#0.02f", valor)
434
+ if float_valor.chars.count >= 7
435
+ float_valor = float_valor.chars.reverse.insert(6, '.').reverse.join
436
+ end
437
+
438
+ if float_valor.chars.count >= 11
439
+ float_valor = float_valor.chars.reverse.insert(10, '.').reverse.join
440
+ end
441
+
442
+ float_valor = float_valor.chars.reverse
443
+ float_valor[2] = ','
444
+
445
+ "R$ #{float_valor.reverse.join}"
446
+ end
461
447
  end