Números Por Extenso: Algoritmo Geral

Introdução

Há algum tempo eu fiz um script em SED que escrevia por extenso um dado número inteiro. Para criar esse script, tive que quebrar um pouco a cabeça, devido a limitação do SED em não fornecer operações aritméticas, mas somente operações em string. Acabou que “criei” um método geral de conversão, que é muito semelhante ao que mostrarei hoje. A vantagem desse método é que ele trata o número de entrada como uma string, nos permitindo converter números extremamente grandes.

Hoje aprenderemos um algoritmo que tem por finalidade escrever por extenso um dado número de entrada.

Primeira parte: um trio por extenso

Primeiramente, vamos analisar como escrever por extenso um número menor que 1000, ou seja, todos os números na faixa [000, 999]. Como não há como gerar os valores por extenso por meio de algoritmos, o único jeito de recuperar esses valores é armazendo-os em algum lugar, tipo uma matriz. Em C, essa matriz é similar a:

/* A palavra dummy é para preencher uma posição
para que cada linha tenha sempre 10 colunas. */
matrizextenso[4][10] = {
{"dummy", "um", "dois", "três", ... , "nove"},
{"dez", "onze, "doze", "treze", ...., "dezenove",
{"dummy", "dummy", "vinte", "trinta", ..., "noventa"},
{"dummy", "cento", "duzentos", "trezentos", ..., "novecentos"}
}

Usando essa matriz, podemos facilmente escrever por extenso um número de três algarismos. Considere o seguinte número: 456. Escrevemos esse número por extenso somente acessando a matriz:

ex: 456
matrizextenso[0][6] = seis
matrizextenso[2][5] = cinquenta
matrizextenso[3][4] = quatrocentos

Observe que os algarismos do número indexam as colunas da matriz enquanto que as linhas são indexadas “manualmente”.

Para números que tem o ‘1’ na casas das dezenas, a indexação é ligeiramente diferente. Considere o número 712:

ex: 712
matrizextenso[1][2] = doze
matrizextenso[3][4] = setecentos

A diferença é que o acesso pelo índice 1 substitui os acessos pelos índices 0 e 2.

Para números com algarismos zeros, a indexação naquela posição não deve ser feita.

ex: 103
matrizextenso[0][3] = três
matrizextenso[3][1] = cento

O índice 2 não foi usado devido o número conter ‘0’ nas casas das dezenas. Outro exemplo:

ex: 002
matrizextenso[0][2] = dois

Infelizmente temos algumas exceções que devem ser tratadas separadamente. São elas: o número 100 (cem) e o 000 (zero).

Com essas regras, podemos montar um pequeno algoritmo para escrever um número de três dígitos por extenso:

definição: escrever_trio_extenso
A entrada é um número da forma: cdu (as letras representam cada algarismo)
A saída é guardada na variável saída

Se trio == "100" : saída = "cem"
senão se trio == "000" : saída = "zero"
senão:
     se c != '0' : saída = matrizextenso[3][c]
     se d == '1':
        saída = saída + matrizextenso[1][u]
     senão:
        se d != '0' : saída = saída + matrizextenso[2][d]
        se u != '0' : saída = saída + matrizextenso[0][u]
     saída = adicionar_conjuncao_e_(saída)

Por fim, devemos adicionar a conjunção ‘e’, como em vinte ‘e’ três. Esse processo não é tão simples quanto parece, principalmente em linguagens que não suportam muitas operações em strings (ex: C). Aqui usei a função adicionar_conjuncao_e() que faz esse papel e sua implementação será ocultada.

Exemplo:

número: 406
Seguindo o algoritmo:

saída = matrizextenso[3][4]
      = quatrocentos

saída = saída + matrizextenso[0][6]
      = quatrocentos seis

saída = adicionar_conjuncao_e(saída)
      = quatrocentos e seis

Qualquer número por extenso: um esboço

Um número grande por extenso nada mais é que conjuntos de três algarismos por extenso. Isso é, para obter esse número maior por extenso, basta dividi-lo em grupos de três e transformar cada grupo separadamente. Veja:

número: 123456789

Divida o número em trios:
  789 : setecentos e oitenta e nove
  456 : quatrocentos de cinquenta e seis
  123 : cento e vinte e três

A saída final consiste na adição das classes (próximo tópico) e da conjunção ‘e’ ou vírgula. É bem mais prático considerar a divisão em trios de modo invertido, como mostra o exemplo, por facilitar as operações.

Segunda Parte: classes

O que chamo de classe aqui é o nome dado a cada elemento do conjunto formado pelas palavras: mil, milhão, bilhão, trilhão, etc. Mais uma vez, como não há como gerar o nome dessas classes dinamicamente, teremos que armazená-las em algum lugar, como um vetor.

*classextenso[] = {"dummy", "mil", "milh", "bilh", "trilh", ...., "vigesilh"}

Os nomes dessas classes foram obtidos da referência [1].

Os nomes das classes estão sem variação em número (plural e singular) para não precisarmos criar um vetor para classes no singular e outro no plural. O próximo passo do algoritmo será como decidir quando usar singular ou plural.

Antes de tudo, vejamos um exemplo:

número: 123 456 789 012 345
 345 : trezentos e quarenta e cinco       0
 012 : doze                               1
 789 : setecentos e oitenta e nove        2
 456 : quatrocentos e cinquenta e seis    3
 123 : cento e vinte e três               4

O número na última coluna nada mais é que um contador de posições. Esse número indexará no vetor de classes qual classe será usada naquele momento. Isso é, se usarmos esse número como índice no vetor classextenso, obteremos a classe desejada. Assim:

número: 123 456 789 012 345

 345 : trezentos e quarenta e cinco       
 012 : doze                               classextenso[1] : mil
 789 : setecentos e oitenta e nove        classextenso[2] : milh
 456 : quatrocentos e cinquenta e seis    classextenso[3] : bilh
 123 : cento e vinte e três               classextenso[4] : trilh

OBS: Para contador igual a zero, nenhuma classe é mapeada (posição “dummy” do vetor).

Um algoritmo simples para obter as classes é:

Para cada trio do número em ordem inversa:
   contador = 0
   Se contador > 0 e trio > 0
      saída = classextenso[contador]
   contador = contador + 1

Terceira parte: plural ou singular

Algo que dificulta em muito a escrita por extenso é a variação de número das classes, pois ora ou outra temos que decidir se usamos, por exemplo, “milhões” ou “milhão”. Em geral, decidir entre esses dois casos é simples: se o trio em questão for igual a ‘1’ usa-se singular, caso contrário usamos plural. Observe que só colocamos plural para classes acima de mil, pois a classe mil sempre vem no singular.

Com isso, para decidirmos se usamos plural ou singular, fazemos:

Para cada trio do número em ordem inversa:
   contador = 0
   Se contador > 0 e trio > 0
      saída = classextenso[contador]
      se contador > 1
         se trio > 1 : saída = saída + "ões"
         senão       : saída = saída + "ão"
   contador = contador + 1

O algoritmo nos diz que só acessamos as classes se o contador for maior que 0, e adicionamos plural ou singular se o contador for maior que 1 (classes acima de mil).

Exemplos:

número: 1789415234001
001 : um                            0
234 : duzentos e trinta e quatro    1 : classextenso[1] = mil
415 : quatrocentos e quinze         2 : classextenso[2] = milh + "ões" (415 != 1)
789 : setecentos e oitenta e nove   3 : classextenso[3] = bilh + "ões" (789 != 1)
001 : um                            4 : classextenso[4] = trilh + "ão" (001 == 1)

Juntando tudo, temos:

um
duzentos e trinta e quatro mil
quatrocentos e quinze milhões
setecentos e oitenta e nove bilhões
um trilhão

Agora só basta juntar as linhas para formar a saída completa. Essa junção é feita pelo conectivo ‘e’ ou pela vírgula, e isso nos leva ao próximo passo.

Quarta Parte: junção dos extensos

De acordo com o gerador de extensos em [2], temos dois modos de juntar os extensos dos trios. Uma é usando ‘e’ e outra, a vírgula.

Basicamente, para cada trio não nulo de um número (exceto o último) e começando do trio mais à direita: se o trio à esquerda for o “000”, usamos o ‘e’. Se o trio dado for menor que 100 também usamos o ‘e’. Caso contrário, para trios maiores ou iguais a 100, usamos a ‘,’.

Espero não ter complicado muito. A seguir um exemplo para esclarecer essa regra:

Exemplo:

número: 1000420055333

001 000 420 055 333
   |   |   |   |
   |   |   |   v
   |   |   |  ',', pois 055 != 0 e 333 >= 100 
   |   |   v           
   |   |  'e', pois 420 != 0 e 055 < 100 
   |   v
   |  'e', pois 000 == 000
   v
  'e', pois 001 != 000 e 001 < 100

Último passo: algoritmo geral

Agora vamos juntar tudo que vimos e elaborar um algoritmo geral para a escrita por extenso.

saída=
extensofinal=
contador=0

Para cada trio do número em ordem inversa:
   se trio > 0
      saída = escrever_trio_extenso(trio)
      se contador > 0
         saída = saída + " " + classextenso[contador]
         se contador > 1:
            se trio > 1 : saída = saída + "ões"
            senão : saída = saída + "ão"
      se nao_e_ultimo_trio()
         se trio_a_esquerda_eq_zero()
            saída = " e " + saida
         senão se trio >= 100
            saída = ", " + saída
         senão
            saída = " e " + saída
      extensofinal = saída + extensofinal
   contador = contador + 1

imprima(extensofinal)

Note que há três funções nesse algoritmo. A escrever_trio_extenso() é a função definida para escrever um trio por extenso. A função nao_e_ultimo_trio() retorna verdadeiro se o trio analisado não é o último trio do número – o trio mais à esquerda. A outra função, trio_a_esquerda_eq_zero(), retorna verdadeiro se o trio à esquerda do trio atual não for zero. Essas duas funções só existem devido a regra de junção de extensos e suas implementações serão ocultadas aqui.

Esse algoritmo pode ser implementado na maioria das linguagens de programação, especialmente àquelas que fornecem um alto poder de tratamento de strings (ex: regex). Para algumas linguagens, algumas restrições devem ser acrescentadas no algoritmo, por exemplo em C, em que o acesso ao vetor classsextenso deve ser controlado para não gerar erro de leitura em memórias não acessíveis.

O script: dextenso.sh

A seguir um shell script que utiliza o algoritmo anterior para escrever um número de entrada por extenso.

#!/bin/bash
# [dextenso.sh]
# Retorna um número de entrada por extenso.
#
# [Autor]
# Marcos Paulo Ferreira (Daemonio)
# undefinido gmail com
# https://daemoniolabs.wordpress.com
#
# [Codificação]
# Script : UTF-8
# Saída  : UTF-8
#
# [Uso]
# $ chmod +x dextenso.sh
# $ ./dextenso.sh <numero>
#
# ex:
# $ ./dextenso.sh 48000069
# quarenta e oito milhões e sessenta e nove
#
# Versão 1.0, by daemonio @ Sun Jun 24 15:19:30 BRT 2012
#

#
# Variáveis globais
#

# Array com os números em extenso.
# OBS: A palavra dummy é só um marcador de
# lugar que assegura uma indexação correta.
trioextenso=( "dummy" "um" "dois" "três" "quatro" "cinco" "seis" "sete"
              "oito" "nove"
              "dez" "onze" "doze" "treze" "quatorze" "quinze" "dezesseis"
              "dezessete" "dezoito" "dezenove"
              "dummy" "dummy" "vinte" "trinta" "quarenta" "cinquenta"
              "sessenta" "setenta" "oitenta" "noventa"
              "dummy" "cento" "duzentos" "trezentos" "quatrocentos"
              "quinhentos" "seiscentos" "setecentos" "oitocentos"
              "novecentos" )

# Array com as classes em extenso.
classextenso=( "dummy" "mil" "milh" "bilh" "trilh" "quatrilh"
               "quintilh" "sextilh" "septilh" "octilh"
               "nonilh" "decilh" "undecilh" "duodecilh"
               "tredecilh" "quatordecilh" "quindecilh"
               "sexdecilh" "setedecilh" "octodecilh"
               "novedecilh" "vigesilh" )

#
# Funções
#

# Escreve um trio por extenso.
function escrever_trio_extenso {
local trio=$1
local saida=
local u= d= c= t=

# Obtém cada algarismo do trio.
c=${trio:0:1}; d=${trio:1:1}; u=${trio:2:1}

case "$trio" in
    100)
        saida='cem'
        ;;
    [0-9]1[0-9]) # Números da forma: x1x.
        t=$((10+$u)); saida=${trioextenso[$t]}
        [ "$c" != '0' ] && t=$((30+$c)) && saida=${trioextenso[$t]}' '$saida
        ;;
    *) # Qualquer número, exceto 100 e os da forma x1x.
        [ "$u" != '0' ] && saida=${trioextenso[$u]}
        [ "$d" != '0' ] && t=$((20+$d)) && saida=${trioextenso[$t]}' '$saida
        [ "$c" != '0' ] && t=$((30+$c)) && saida=${trioextenso[$t]}' '$saida
        ;;
esac

# Adiciona a conjução 'e'. Basicamente insere
# a string " e " no lugar dos espaços que não
# estejam no início ou no fim.
saida=$(sed 's/^ *//;s/ *$//;s/ / e /g'<<<"$saida")
echo "$saida"
}

# Retorna verdadeiro se o trio dado
# não é o último. Para isso, ela recebe
# o número dado no primeiro parâmetro e
# o contador no segundo.
function nao_e_ultimo_trio {
local numero=$1
local contador=$2

(( $contador < ((${#numero}/3)-1) ))
}

# Retorna verdadeiro se o trio à esquerda
# do trio dado não é zero. Para isso, ela
# recebe o número dado no primeiro
# parâmetro e o contador no segundo.
function trio_esquerda_eq_zero {
local numero=$1
local contador=$2
local t=$(( ${#numero} - 3- 3*(contador+1)))

(( 10#${numero:$t:3} == 0 ))
}

# Função principal. Ela representa o algoritmo final
# visto no post.
function dextenso {
local numero=$1
local contador=0
local saida=
local extensofinal=
local t=

# Retira os zeros iniciais
numero=$(sed 's/^0*//'<<<$numero)

# Número zero.
[ -z $numero ] && echo zero && return 0

# Padding para que a quantidade de algarismos desse
# número seja múltipla de três.
t=$((3-${#numero}%3))
[ $t != 3 ] && numero=$(echo '000' | cut -c1-$t)$numero

# Para cada trio na ordem inversa do número...
for trio in $(echo "$numero" | rev | sed 's/.../&\n/g')
do
    # Obtém a ordem normal do trio usando o comando
    # rev novamente.
    trio=$(rev<<< "$trio")

    if [ "$trio" -ne 0 ]
    then
        # Extenso de um trio.
        saida=$(escrever_trio_extenso $trio)

        # Classes + plural.
        if [ "$contador" -gt 0 ]
        then
            saida="$saida"' '"${classextenso[$contador]}"
            if [ "$contador" -gt 1 ]
            then
                [ "$trio" -gt 1 ] && saida="$saida"'ões' || saida="$saida"'ão'
            fi
        fi

        # Junção.
        if nao_e_ultimo_trio $numero $contador
        then
            if trio_esquerda_eq_zero $numero $contador
            then
                saida=" e "$saida
            elif [ $trio -ge 100 ]; then
                saida=", "$saida
            else
                saida=" e "$saida
            fi
        fi

        # Monta a saída.
        extensofinal="${saida}${extensofinal}"
    fi

    let contador++
done

# Fim do algoritmo. Mostra a saída.
echo "$extensofinal"
}

function show_help {
echo 'dextenso - by daemonio'
echo '[uso] ./dextenso.sh <numero>'
echo
exit 1
}

#
# Main
#

# Checa parâmetros.
[ -z "$1" ] && show_help

# Retorna '2' se o parâmetro passado
# não é um número.
[[ "$1" =~ ^[0-9]*$ ]] || exit 2

# Chama a função, passando o número de
# entrada como parâmetro.
dextenso $1

#EOF

Exemplos:

$ chmod +x dextenso.sh
$ ./dextenso.sh 78991100101
setenta e oito bilhões, novecentos e noventa e um milhões, cem mil, cento e um

$ ./dextenso.sh 1000450002578
um trilhão e quatrocentos e cinquenta milhões e dois mil, quinhentos e setenta e oito

$ ./dextenso.sh 1989 
um mil, novecentos e oitenta e nove

Conclusão

A vantagem do método apresentado é que ele considera o número de entrada como uma string, e graças a isso, não estamos limitados às restrições que determinas linguagens impõem no tratamento de números, como tamanho máximo para não ocorrer overflow.
Vale ressaltar mais uma vez que o método apresentado foi inteiramente baseado na saída do gerador do link [2], e é provável que eu tenha deixado alguma coisa passar (ou me baseado na referência errada). Se você encontrou algum erro na saída do script, por favor o relate nos comentários.

Referências

[1] Escalas curta e longa by wipedia (Acessado em: Junho/2012)
http://pt.wikipedia.org/wiki/Escalas_curta_e_longa

[2] Calculadora de Números Decimais por Extenso by Matemática Didática (Acessado em: Junho/2012)
http://www.matematicadidatica.com.br/CalculadoraNumerosDecimaisPorExtenso.aspx

12 pensamentos sobre “Números Por Extenso: Algoritmo Geral

  1. Pingback: Código De Número Por Extenso Em Java | Daemonio Labs

  2. Pingback: Biblioteca em C: Números por Extenso | Daemonio Labs

  3. Muito bom o Post, estou começando o curso de TADS e vai me ajudar bastante. Mas o link da biblioteca não esta disponível poderia indicar outro link pra baixá-lo?

  4. Pingback: Classe Python de Números Por Extenso | Daemonio Labs

  5. Um algorítimo muito bom, parabéns.
    Verifiquei que há implementação deste algorítimo, no site de vocês, em C, phyton e Java. Como precisava em VB6, eu mesmo implementei.
    Segue abaixo a função em VB6. OBS: Eu preferi usar todas as “partes” do algorítimo em um função unica e não usei exatamente a mesma nomenclatura. No geral, a lógica é a mesma e está muito parecido com o original.
    [ ]s

    Private Function Extenso(Num As String) As String
      Dim Numeros(5)
      Dim NumExtenso, Trio, TrioExtenso, c, d, u As String
      Dim iClasse As Integer
      
      Numeros(1) = Array("dummy", "um", "dois", "três", "quatro", "cinco", "seis", "sete", "oito", "nove")
      Numeros(2) = Array("dez", "onze", "doze", "treze", "quatorze", "quinze", "dezesseis", "dezessete", "dezoito", "dezenove")
      Numeros(3) = Array("dummy", "dummy", "vinte", "trinta", "quarenta", "cinquenta", "sessenta", "setenta", "oitenta", "noventa")
      Numeros(4) = Array("dummy", "cento", "duzentos", "trezentos", "quatrocentos", "quinhentos", "seiscentos", "setecentos", "oitocentos", "novecentos")
      Numeros(5) = Array("dummy", "mil", "milh", "bilh", "trilh", "quatrilh", "quintilh", "sextilh", "septilh", "octilh", "nonilh", "decilh", "undecilh", "duodecilh", "tredecilh", "quatordecilh", "quindecilh", "sexdecilh", "setedecilh", "octodecilh", "novedecilh", "vigesilh")
      
      'Verifica se todos os elementos de Num são iguais a "0"
      For i = 1 To Len(Num)
         iClasse = iClasse + Val(Mid(Num, i, 1))
      Next
      If iClasse = 0 Then
         Extenso = "zero"
         Exit Function
      End If
      
      iClasse = 0
      
      Do While Num  ""
         Trio = Right("00" + Right(Num, 3), 3) 'pega o ultimo trio de numeros, com zeros a esquerda se necessario
         c = Mid(Trio, 1, 1) 'centena
         d = Mid(Trio, 2, 1) 'dezena
         u = Mid(Trio, 3, 1) 'unidade
         
         If Trio = "000" Then
            TrioExtenso = ""
         ElseIf Trio = "100" Then
            TrioExtenso = "cem"
         Else
            'Extenso no casa das centenas
            TrioExtenso = IIf(c = "0", "", Numeros(4)(Val(c)))
            
            'Extenso no casa das Dezenas
            If (d = "1") Then
               If TrioExtenso = "" Then
                  TrioExtenso = Numeros(2)(Val(u))
               Else
                  TrioExtenso = TrioExtenso + " e " + Numeros(2)(Val(u))
               End If
            Else
               If (d  "0") Then
                  If TrioExtenso = "" Then
                     TrioExtenso = Numeros(3)(Val(d))
                  Else
                     TrioExtenso = TrioExtenso + " e " + Numeros(3)(Val(d))
                  End If
               End If
               
               'Extenso no casa das unidades
               If (u  "0") Then
                  If TrioExtenso = "" Then
                     TrioExtenso = Numeros(1)(Val(u))
                  Else
                     TrioExtenso = TrioExtenso + " e " + Numeros(1)(Val(u))
                  End If
               End If
            End If
         End If
         
         'Concatena o extenso do trio no extenso do numedo todo...
         If iClasse = 0 Then
            NumExtenso = TrioExtenso
         ElseIf iClasse = 1 Then
            NumExtenso = IIf(TrioExtenso = "", "", TrioExtenso + " mil " + NumExtenso)
         Else
           If TrioExtenso  "" Then
              If Val(Trio) = 1 Then
                 NumExtenso = TrioExtenso + " " + Numeros(5)(iClasse) + "ão " + NumExtenso
              Else
                 NumExtenso = TrioExtenso + " " + Numeros(5)(iClasse) + "ões " + NumExtenso
              End If
           End If
         End If
         
         iClasse = iClasse + 1 'posicao atual no array Numeros(5), para cada trio de numeros processado
         
         'Remove o ultimo trio de numeros de Num. Se o tamanho de Num < 3, seta Num para "", para finalizar o loop.
         If Len(Num) < 3 Then
            Num = ""
         Else
            Num = Mid(Num, 1, Len(Num) - 3)
         End If
      Loop
      
      Extenso = Trim(NumExtenso)
    End Function
    
    • OBS: O HTML removeu o sinal “Menor Maior”. onde se lê por exemplo
      (d “0”), leia-se (d != “0”). O sinal “!=” nao pode ser usado no VB6 para representar “diferente”.

    • MUITO BOM Sidney. Coloquei seu código no pastebin:

      http://pastebin.com/tcnE4CPF

      não sei se ele está correto. Apenas coloquei o sinal de diferente (sinal de menor + sinal de maior) nas linhas 45, 54 e 70. Veja se é isso mesmo. Se tiver errado eu corrijo.

      O wordpress consome automaticamente algumas tags em html por isso o problema.

      Abraços

    • Olá,
      a biblioteca é só para modular o programa para deixá-lo mais flexível e de fácil manutenção. O código da biblioteca também
      se encontra na postagem.

      Muita gente tem dificuldade de compilar mais de um arquivo nos compiladores Windows, se esse for seu caso, copie todos os códigos (biblioteca (.c) + o principal) e cole em um só arquivo e depois mande compilar. Isso é só uma dica, não deve ser considerada como regra, pois o mais comum é compilar vários fontes em um dado projeto.

Deixe um comentário