Como converter números por extenso na prática
A leitura de numeros por extenso é um desses processos que parece simples até você tentar automatizar. Eu comecei porque precisava transformar valores de notas fiscais em texto para gerar relatórios em PDF. O problema real não é converter 1.234. A dificuldade aparece quando os números ganham centavos, quando o sistema precisa lidar com exceções regionais ou quando a formatação bancária exige regras específicas. A abordagem mais eficiente que encontrei envolve construir uma tabela base com os números de 0 a 999 e depois aplicar a lógica de escalas. Cada bloco de três dígitos recebe um sufixo: mil, milhão, bilhão. O que a maioria dos tutoriais não explica é que o tratado é diferente de centena. "Trezentos e cinquenta e dois" versus "mil trezentos e cinquenta e dois" — o "e" aparece em posições diferentes dependendo da escala, e errar isso quebra todo o formato.
Leitura de numeros por extenso: o que ninguém conta
Existem duas armadilhas que todo mundo pega. A primeira é a questão do "cem" versus "cento". Cem só existe quando é o número exato. Qualquer coisa acima vira centena: 101 é cento e um, 199 é cento e noventa e nove. Isso parece óbvio até aparecer no código como um caso especial que você esquece de tratar. A segunda é a concordância de gênero quando o valor vem acompanhado de moeda. "Um real" não é "uma real". "Duzentos reais" funciona, mas "uma mil real" soa errado porque o correto seria "uma mil" no feminino. A maioria das bibliotecas resolve isso com dicionários separados para real e real, mas a concordância com centena continua sendo um problema que raramente é documentado.
Eu tive um caso específico no qual precisei lidar com valores como 1.000.000,00. O número em si é reto, mas quando a entrada vinha de um sistema que formatava com ponto de milhar e vírgula decimal, eu precisava normalizar antes de qualquer processamento. O workaround que funcionou foi limpar a string removendo tudo que não fosse dígito ou vírgula, detectar qual era o separador decimal pelo contexto do sistema e só então converter para float. Se o número tivesse milhões e houvesse casas decimais, eu dividia por 100 antes de processar a parte inteira e a parte fracionária separadamente. Isso eliminou erros que apareciam esporadicamente nos relatórios — tipos onde o valor 2.500,75 virava "dois mil quinhentos e setenta e cinco" sem os centavos.
Implementação básica
O núcleo do algoritmo funciona assim. Você cria arrays com os nomes dos números de 0 a 19, depois dezenas de 20 a 90 multiplicadas por dez, e centenas de 100 a 900. Para cada bloco de três dígitos, você decomõe em centena, dezena e unidade e monta a string. O resultado final junta os blocos com as escalas correspondentes. Aqui está uma versão funcional em JavaScript que cobre os casos comuns:
👉 Clique no botão abaixo para saber mais sobre o assunto!
const unidades = ['zero', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove', 'dez', 'onze', 'doze', 'treze', 'quatorze', 'quinze', 'dezesseis', 'dezessete', 'dezoito', 'dezenove'];
const dezenas = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa'];
const centenas = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos']; function converter(num) {
if (num === 0) return 'zero';
if (num === 100) return 'cem';
let resultado = '';
let escala = ['', 'mil', 'milhão', 'bilhão'];
let indiceEscala = 0;
while (num > 0 && indiceEscala < 4) {
let bloco = num % 1000;
num = Math.floor(num / 1000);
let strBloco = '';
let c = Math.floor(bloco / 100);
bloco %= 100;
let d = Math.floor(bloco / 10);
let u = bloco % 10;
if (c > 0) {
strBloco = centenas[c];
if (d > 0 || u > 0) strBloco += ' e ';
}
if (d === 1) {
strBloco += unidades[bloco];
} else {
if (d > 0) strBloco += dezenas[d];
if (u > 0) {
if (strBloco && d > 0) strBloco += ' e ';
strBloco += unidades[u];
}
}
if (strBloco) {
if (indiceEscala > 0 && bloco !== 1) strBloco += ' ' + escala[indiceEscala];
if (indiceEscala > 0 && strBloco && resultado) strBloco = strBloco + ' ' + resultado;
resultado = strBloco;
}
indiceEscala++;
}
return resultado;
}
Esse código converte números inteiros. Para valores monetários com centavos, você precisa adicionar uma verificação: se o número tiver casas decimais, divide a parte inteira da parte fracionária, converte cada uma separadamente e junta com a preposição adequada. "Mil e quinhentos e trinta e dois reais e quarenta e cinco centavos". Note que "real" varia para "reais" no plural e "centavo" também, e aqui entra a segunda dificuldade que poucos mencionam: quando a parte inteira é 1 e a parte fracionária também é 1, fica "um real e um centavo". Quando a fracionária é 0, não coloca "zero centavos" — isso é erro comum em implementações iniciantes.
Limitações e quando usar alternativa
Essa abordagem manual funciona bem para valores até bilhões. Acima disso, a complexidade cresce exponencialmente porque o português tem regras específicas para trilhão, quatrilhão e assim por diante. Além disso, a biblioteca nativa do Node.js Intl.NumberFormat com locale pt-BR e style: 'currency' já faz boa parte do trabalho se você estiver num ambiente web. O problema é que ela retorna strings formatadas pelo browser ou runtime, e você não tem controle fino sobre o output. Se precisa de padronização absoluta — por exemplo, para validação de documentos fiscais — o manualmente escrito ainda é mais confiável. Outra limitação séria é que a leitura de numeros por extenso em português brasileiro tem variações que variam por região. Em alguns contextos, "onze" pode ser considerado formal demais e "dez e um" mais natural. O algoritmo acima segue a norma padrão, mas se o seu sistema for usado por pessoas de diferentes regiões, considere adicionar mapeamentos opcionais ou deixar a configuração exposta.
O tempo de desenvolvimento de uma implementação completa e testada costuma ficar entre 4 e 8 horas, dependendo do nível de detalhe que você quer. Usar uma biblioteca pronta economiza esse tempo, mas cobra na flexibilidade. A escolha depende do quanto você precisa controlar o formato de saída.