DEV Community

Cover image for Normalização de Números para Dados Internacionais: Um Guia Prático
circobit
circobit

Posted on

Normalização de Números para Dados Internacionais: Um Guia Prático

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;
}
Enter fullscreen mode Exit fullscreen mode

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, "")
Enter fullscreen mode Exit fullscreen mode

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:

  1. Dados científicos com exatamente 3 decimais: "3.141" vira "3141"
  2. Preços abaixo de $10 com 3 casas decimais: "$1.234" vira "$1234"
  3. 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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Cuidado com Entidades HTML

Tabelas web às vezes contêm &nbsp; (espaço não quebrável) dentro de números:

// "1&nbsp;234&nbsp;567" deve virar "1234567"
.replace(/&nbsp;/g, " ")
.replace(/\s+/g, "")
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)