Você exporta uma tabela de um site alemão. A coluna de receita mostra "1.234,56 €".
Você cola no Excel. Vira "1.23456E" ou, pior, uma data.
Bem-vindo ao problema de formato numérico internacional.
O Problema Central: O Que "1.234" Significa?
Nos EUA e Reino Unido: 1.234 = um ponto dois três quatro (decimal)
Na Alemanha, Espanha e maior parte da América Latina: 1.234 = mil duzentos e trinta e quatro
Mesmos caracteres. Valores completamente diferentes.
Ao extrair dados de sites internacionais, você não pode assumir qual formato está sendo usado. E errar significa que sua análise estará errada por ordens de grandeza.
A Ambiguidade dos Três Dígitos
O caso mais difícil é exatamente três dígitos após um separador.
| Entrada | Interpretação EUA | Interpretação UE |
|---|---|---|
| 1.234 | 1.234 (decimal) | 1.234 (milhares) |
| 1,234 | 1,234 (milhares) | 1.234 (decimal) |
| 1.234.567 | Inválido | 1.234.567 |
| 1,234,567 | 1,234,567 | Inválido |
Com dois dígitos depois, é claramente decimal: "1.23" ou "1,23".
Com quatro ou mais dígitos depois, é claramente decimal: "3.14159".
Com três dígitos? Pode ser qualquer um.
O Algoritmo de Detecção
Aqui está uma abordagem heurística que lida com a maioria dos casos do mundo real:
function normalizeNumberString(value) {
if (typeof value !== "string") return value;
const v = value.trim();
if (!v || !/[0-9]/.test(v)) return value;
// Passo 1: Limpar símbolos de moeda, porcentagens, espaços
let cleaned = v
.replace(/ /g, " ")
.replace(/^(USD|EUR|GBP|R\$|MXN)\s*/i, "")
.replace(/[$€£¥₹₽₩]/g, "")
.replace(/%/g, "")
.replace(/\s+/g, "");
// Passo 2: Tratar sinais negativos
const isNegative = cleaned.match(/^-/);
if (isNegative) {
cleaned = cleaned.substring(1).trim();
}
// Passo 3: Apenas dígitos? Pronto
if (/^\d+$/.test(cleaned)) {
return isNegative ? `-${cleaned}` : cleaned;
}
// Passo 4: Detectar separadores
const hasComma = cleaned.includes(",");
const hasDot = cleaned.includes(".");
// Passo 5: Aplicar heurística
let decimalSeparator = ".";
let thousandsSeparator = ",";
if (hasComma && hasDot) {
// Ambos presentes: o último é decimal
const lastComma = cleaned.lastIndexOf(",");
const lastDot = cleaned.lastIndexOf(".");
if (lastComma > lastDot) {
decimalSeparator = ",";
thousandsSeparator = ".";
}
} else if (hasComma && !hasDot) {
const parts = cleaned.split(",");
if (parts.length === 2 && parts[1].length <= 2) {
// "1,23" → decimal
decimalSeparator = ",";
} else {
// "1,234" ou "1,234,567" → milhares
decimalSeparator = null;
thousandsSeparator = ",";
}
} else if (hasDot && !hasComma) {
const parts = cleaned.split(".");
if (parts.length === 2) {
const afterDot = parts[1];
if (afterDot.length <= 2 || afterDot.length >= 4) {
// "1.23" ou "3.14159" → decimal
decimalSeparator = ".";
} else {
// "1.234" → MILHARES (escolha pragmática para tabelas)
decimalSeparator = null;
thousandsSeparator = ".";
}
} else {
// Múltiplos pontos → milhares
decimalSeparator = null;
thousandsSeparator = ".";
}
}
// Passo 6: Limpar separadores de milhares
if (thousandsSeparator) {
cleaned = cleaned.replace(new RegExp(`\\${thousandsSeparator}`, "g"), "");
}
// Passo 7: Normalizar decimal para ponto
if (decimalSeparator && decimalSeparator !== ".") {
cleaned = cleaned.replace(decimalSeparator, ".");
}
// Passo 8: Validar
const num = Number(cleaned);
if (!Number.isFinite(num)) return value;
return isNegative ? `-${cleaned}` : cleaned;
}
Por Que Três Dígitos = Milhares
A decisão-chave: quando vemos exatamente três dígitos após um único separador (como "1.234"), interpretamos como milhares, não decimal.
Por quê? Em tabelas HTML:
- Dados financeiros com milhares são extremamente comuns: $1.234, €1.234
- Decimais com exatamente três dígitos são raros na prática
- Dados científicos com três decimais (como "3.141") geralmente são escritos como "3.14159" ou simplesmente "3.14"
Essa heurística é um trade-off pragmático que funciona para a maioria das tabelas do mundo real.
A Matriz Completa de Formatos
| Entrada | Interpretação | Saída Normalizada |
|---|---|---|
| 1.234,56 | Decimal UE | 1234.56 |
| 1,234.56 | Decimal EUA | 1234.56 |
| 1.234.567,89 | UE milhares + decimal | 1234567.89 |
| 1,234,567.89 | EUA milhares + decimal | 1234567.89 |
| 1.234 | Milhares | 1234 |
| 1,234 | Milhares | 1234 |
| 3.14 | Decimal | 3.14 |
| 3,14 | Decimal | 3.14 |
| 3.14159 | Decimal (4+ dígitos) | 3.14159 |
| $ 1.200,50 | Moeda + UE | 1200.50 |
| -5.000 | Milhares negativos | -5000 |
| 12.5% | Porcentagem | 12.5 |
Tratando Códigos de Moeda
Dados internacionais vêm com prefixos e sufixos de moeda. Limpe-os primeiro:
// Remover códigos de moeda ANTES dos símbolos
.replace(/^(USD|EUR|GBP|JPY|CHF|CAD|AUD|CNY|INR|BRL|R\$|MXN|KRW)\s*/i, "")
// Depois remover símbolos
.replace(/[$€£¥₹₽₩₪฿₫₴₦]/g, "")
A ordem importa: "R$" (Real brasileiro) contém "$", então remover códigos de moeda primeiro evita correspondências parciais.
Quando a Heurística Falha
Nenhuma heurística é perfeita. Você terá resultados incorretos quando:
- Dados científicos com exatamente 3 decimais: "3.141" vira "3141"
- Preços abaixo de $10 com 3 casas decimais: "$1.234" vira "$1234"
- Formatos mistos em uma coluna: Algumas linhas UE, algumas linhas EUA
Para o caso 3, você precisa de detecção de formato no nível da coluna:
function detectColumnFormat(values) {
let euIndicators = 0;
let usIndicators = 0;
for (const v of values) {
if (/\d\.\d{3},\d{2}$/.test(v)) euIndicators++;
if (/\d,\d{3}\.\d{2}$/.test(v)) usIndicators++;
}
if (euIndicators > usIndicators) return "eu";
if (usIndicators > euIndicators) return "us";
return "auto"; // Usar heurística por célula
}
Perfis de Exportação para Diferentes Regiões
Quando você conhece o país de origem, pode evitar a ambiguidade inteiramente usando perfis específicos por região:
- Formato europeu: Assume vírgula como separador decimal, usa ponto e vírgula como delimitador CSV
- Formato EUA/UK: Assume ponto como separador decimal, usa vírgula como delimitador CSV
O HTML Table Exporter inclui perfis pré-configurados para ambos os formatos, além de perfis especializados para ferramentas como Pandas e DuckDB.
Armadilhas Comuns
Não Perca Precisão
// Errado: perde precisão em números grandes
const num = parseFloat("12345678901234567890");
// 12345678901234567000 (limite do JavaScript)
// Melhor: manter como string até o cálculo final
const cleaned = normalizeNumberString(value);
// Só usar parseFloat quando precisar calcular
Cuidado com Entidades HTML
Tabelas web às vezes contêm (espaço não quebrável) dentro de números:
// "1 234 567" deve virar "1234567"
.replace(/ /g, " ")
.replace(/\s+/g, "")
Preserve o Original Quando Houver Dúvida
Se o valor não parece um número, retorne-o inalterado:
if (!/[0-9]/.test(v)) return value;
// ...
if (!Number.isFinite(num)) return value;
Melhor deixar "N/A" como "N/A" do que tentar interpretá-lo.
Resumo
| Cenário | Detecção | Notas |
|---|---|---|
| Ambos . e , | O último = decimal | Universal |
| Apenas vírgula, ≤2 dígitos depois | Decimal | "1,23" |
| Apenas vírgula, 3+ dígitos depois | Milhares | "1,234" |
| Apenas ponto, ≤2 ou 4+ dígitos | Decimal | "1.23", "3.14159" |
| Apenas ponto, exatamente 3 dígitos | Milhares | "1.234" |
O caso de três dígitos é o trade-off pragmático. Funciona para a maioria dos dados do mundo real. Na dúvida, use um perfil de exportação que corresponda à sua região de origem.
Para saber mais sobre como copiar dados de sites para Excel sem problemas de formatação, veja nosso guia sobre as melhores extensões Chrome para exportar tabelas.
Precisa de exportações numéricas limpas sem adivinhação? Saiba mais em gauchogrid.com/pt-br/html-table-exporter ou experimente gratuitamente na Chrome Web Store.
Top comments (0)