Como implementar leitura por extenso de números sem perder horas
A primeira coisa que você precisa entender é que esse problema tem uma armadilha clássica. A maioria dos desenvolvedores tenta resolver com uma única função que percorre o número por extenso usando dicionários e condicionais encadeadas. Funciona até cem mil. Depois disso, algo quebra silenciosamente porque esqueceu de inserir um "e" ou confundiu a regra de "cento" com "cents". Eu passei duas semanas corrigindo bugs em um sistema financeiro por causa exatamente disso. O erro estava na posição do "e" entre centena e dezena quando a dezena era zero: 101 vira "cento e um" mas 100 vira apenas "cem", não "cem e um". Parece simples, mas a lógica se complica rapidamente.
O que é leitura por extenso de numeros
É o processo de converter uma representação numérica em sua forma textual escrita, seguindo as regras ortográficas do idioma. No português brasileiro, isso significa lidar com uma hierarquia de classes numéricas: unidades, dezenas, centenas, milhares, milhões, bilhões, trilhões. Cada classe tem suas próprias regras de conjunção. A classe dos milhares sempre exige um "mil" no final quando o grupo não é zero, mas os milhões e bilhões exigem o plural correto: "dois milhões", "três bilhões". Esses plurais são onde mais gente erra. A estrutura básica funciona em grupos de três dígitos, processados da direita para a esquerda. Cada grupo é convertido separadamente e depois recebe o sufixo da classe correspondente. O resultado é montado da esquerda para a direita, juntando os grupos já formatados. Parece óbvio, mas a implementação exige cuidado com cases como zero absoluto, números negativos e decimais, que mudam completamente a saída esperada.
A abordagem prática que eu uso
Em vez de construir uma máquina de estados, eu dividi o problema em três camadas bem separadas. A primeira camada lida com a parte inteira, a segunda com a parte decimal quando existe, e a terceira faz a montagem final aplicando as regras de concordância. Isso permite testar cada parte isoladamente e facilita a manutenção quando o comportamento muda. Camada 1: unidades de 0 a 99
Essa é a base de tudo. Números de zero a dezenove são lexiconais, ou seja, têm formas próprias que não seguem nenhum padrão morfológico. Vinte a noventa são formados pelo radical da dezena mais "e" mais a unidade, exceto quando a unidade é zero. O número 100 tem um tratamento especial: é "cem", não "cento", porque "cento" só aparece quando há seguido de outro termo, como "cento e um". Camada 2: grupos de três dígitos
Aqui você pega um número entre 0 e 999 e devolve o texto correspondente. A lógica é: se for menor que 100, chama a camada 1. Se for maior ou igual a 100, trata a centena separadamente e concatena com o restante. A centena segue o padrão: 100 a 199 usam "cento" exceto 100 que é "cem"; 200 é "duzentos"; 300 "trezentos"; e assim por diante até 900. Depois vem o "e" obrigatório entre centena e dezena apenas quando a dezena não for zero. Esse é o bug clássico que eu mencionei. Camada 3: classes numéricas
Depois de ter o grupo de três dígitos convertido, você precisa aplicar a classe: mil, milhão, bilhão, trilhão. A regra de plural é simples mas exige verificação: "um milhão" no singular, "dois milhões" no plural. O mesmo vale para bilhão e trilhão. Milhar é diferente porque não tem forma plural regular no sentido usual — "dois mil", "trezentos mil" sempre usam "mil" invariável. EsseInvariantibilidade é importante e fácil de esquecer.
Um problema real que eu encontrei
Estava implementando o módulo para uma corretora de valores quando descobri que números exatamente múltiplos de mil causavam redundância. 1000 virava "mil" mas 1001 virava "mil e um", o que está correto. O problema era com 1.000.000: o algoritmo ia gerar "um milhão" e depois, ao processar o grupo dos milhares (que era zero), ia appendar algo como "zero mil" ou simplesmente pular. A saída final ficava inconsistente dependendo de como o código tratava grupos vazios. Minha solução foi criar uma flag que ignora grupos zero entre grupos não zero, mas mantém o grupo dos milhares quando ele é o único não nulo. Testei manualmente com cinquenta casos extremos antes de fechar o código.
Dicas que ninguém conta
A variável de entrada não deve ser string. Se você passar o número como string, já perdeu. Trabalhe sempre com o tipo numérico nativo do seu ambiente. Em Python, use int ou Decimal. Em JavaScript, Number com cuidado com precisão, ou melhor, BigInt para valores grandes. Conversões de float para string introduzem erros de representação como 1.1 virando "1.1000000000000001". Use Decimal para dinheiro e valores financeiros. O segundo ponto é sobre limites. Números muito grandes como trilhões e quatrilhões raramente aparecem em sistemas do dia a dia, mas quando aparecem — geralmente em relatórios governamentais ou dados astronômicos — a maioria das bibliotecas existentes quebra. Eu recomendo definir um limite superior claro no seu sistema e lançar exceção ou tratar explicitamente quando ultrapassar. É melhor falhar visivelmente do que produzir uma saída errada e silenciosa.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Bibliotecas prontas vs. implementação própria Existem bibliotecas para várias linguagens. No Brasil, a principal referência é a que segue a norma do Banco Central para conversão de valores em documentos fiscais. Se seu uso for para notas fiscais, cheques ou contratos, use uma biblioteca validada. A implementação própria é viável para uso interno onde a margem de erro é aceitável, mas para produção financeira eu não recomendo. O custo de desenvolvimento e teste não compensa quando existem opções maduras disponíveis.
Para quem quer construir do zero, o tempo médio de desenvolvimento de uma versão robusta com testes cobre cases edge é de aproximadamente uma semana para alguém com experiência. A versão inicial funciona em dois dias, mas a versão que não quebra em produção leva muito mais tempo.
Exemplo de saída em diferentes cenários
0 zero 1 um
15 quinze 21 vinte e um
100 cem 101 cento e um
1.000 mil 1.001 mil e um
1.500 mil e quinhentos 1.000.000 um milhão
2.500.000 dois milhões e quinhentos mil 1.234.567.890 um bilhão, duzentos e trinta e quatro milhões, quinhentos e sessenta e sete mil, oitocentos e noventa
Note que em português brasileiro não usamos vírgulas na leitura por extenso. A pontuação na escrita do por extenso segue o fluxo natural da frase, sem listas separadas por vírgula. Isso diferencia o português do inglês, que usa "one billion, two hundred thirty-four million..." com vírgulas separando as classes. Se você estiver traduzindo de outra língua, esse detalhe mata. A limitação mais importante da leitura por extenso de numeros é que ela não lida bem com formatos monetários complexos como "R$ 1.234,56" se a biblioteca não esperar essa entrada. Você precisa parsing separado para a parte inteira e decimal antes de chamar a conversão. Sem isso, números com virgula decimal vão falhar ou produzir resultados sem sentido. Separe isso em duas etapas claras no seu pipeline.