sqlpostgresqlroundnumeric

ROUND no SQL: arredondar para o inteiro mais proximo, arredondamento bancario e dinheiro

Como o ROUND arredonda para o inteiro mais proximo, por que half-away-from-zero difere do bancario e por que numeric supera double para dinheiro.

2 min de leituraReferencesql · postgresql · round · numeric · money · clickhouse

ROUND arredonda um numero para o inteiro mais proximo. Parece trivial, mas a funcao esconde duas estrategias distintas para lidar com as metades e uma diferenca traicoeira entre o tipo exato numeric e o aproximado double precision, exatamente a que quebra relatorios financeiros sem aviso.

Arredondamento basico para inteiro

Com um unico argumento, ROUND descarta a parte decimal e salta para o inteiro mais proximo. As metades (.5) vao se afastando do zero: 2.5 vira 3 e -2.5 vira -3.

SELECT
  ROUND(3.14159) AS a,   -- 3
  ROUND(2.5)     AS b,   -- 3
  ROUND(3.5)     AS c,   -- 4
  ROUND(-2.5)    AS d;   -- -3

Propriedades principais:

  • Para numeric o resultado mantem o tipo, a parte decimal simplesmente vai a zero.
  • A estrategia para literais como 2.5 e half-away-from-zero, simetrica em relacao ao zero.
  • Para manter casas decimais existe um segundo argumento ROUND(x, n), tratado em outro artigo.

numeric vs double: onde o arredondamento bancario se esconde

A grande pegadinha e que o PostgreSQL arredonda as metades de forma diferente conforme o tipo. O literal 2.5 e numeric e se afasta do zero. Mas double precision usa arredondamento bancario (round-half-to-even): as metades saltam para o inteiro par mais proximo.

SELECT
  ROUND(2.5::numeric)          AS num_25,   -- 3
  ROUND(3.5::numeric)          AS num_35,   -- 4
  ROUND(2.5::double precision) AS dbl_25,   -- 2
  ROUND(3.5::double precision) AS dbl_35;   -- 4

Com double, tanto 2.5 quanto 3.5 arredondam para 2 e 4 porque o vizinho par vence sob half-to-even. Nao e um bug, e um jeito de cancelar o vies sistematico que surge ao somar muitos valores arredondados.

Pegadinha: o mesmo numero da um resultado diferente apenas por causa do tipo. Se a sua consulta mistura constantes e colunas float, os totais podem dancar um centavo. Faca cast para numeric de forma explicita quando a previsibilidade importar.

Arredondando dinheiro

Para dinheiro, double e perigoso por um segundo motivo: nem toda fracao decimal e representavel em ponto flutuante binario. Compare um total acumulado:

SELECT
  SUM(amount)            AS raw_total,
  ROUND(SUM(amount))     AS rounded_total
FROM orders
WHERE status = 'paid';

Se amount for declarado numeric(12,2), tudo e exato. Se for double precision, voce ja pode carregar caudas como 19.999999998. A regra e simples: guarde dinheiro como numeric e aplique ROUND sobre numeric.

Arredondar o salario medio por departamento para inteiro:

SELECT
  dept,
  ROUND(AVG(salary)) AS avg_salary
FROM employees
GROUP BY dept
ORDER BY avg_salary DESC;

AVG sobre uma coluna numeric retorna numeric, entao o arredondamento aqui usa previsivelmente half-away-from-zero.

Diferencas no MySQL e ClickHouse

ROUND existe em todo lugar, mas a estrategia das metades muda.

  • MySQL: ROUND(x) para tipos exatos (DECIMAL) arredonda se afastando do zero, enquanto para tipos aproximados (DOUBLE) o comportamento depende da biblioteca C subjacente, normalmente arredondamento bancario. A mesma divisao do PostgreSQL.
SELECT ROUND(2.5), ROUND(2.5e0);
-- 3 (DECIMAL, away from zero), often 2 (DOUBLE, to even)
  • ClickHouse: round() usa por padrao arredondamento bancario, para o inteiro par mais proximo. Se voce quer o comportamento familiar de se afastar do zero, use roundBankers() para deixar claro ou funcoes dedicadas como floor/ceil.
SELECT round(2.5), round(3.5), roundBankers(2.5);
-- 2, 4, 2

Lembre-se de uma coisa: "arredondar para inteiro" nao e uma operacao unica, mas uma familia de estrategias. Antes de confiar ao ROUND um relatorio financeiro, verifique o tipo do argumento e como o seu banco trata as metades exatas.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador