Fala galera!
Nesse artigo, vou compartilhar com vocês alguns dicas e explicações sobre arredondamento de números no SQL Server, utilizando ROUND, FLOOR, CEILING e também, utilizando uma função personalizada para atender às definições da norma ABNT NBR 5891.
Introdução
Presente em praticamente todos os sistemas de informação, as funções de arredondamento são muito utilizadas para lidar com números fracionados e decimais, especialmente com moeda ($). Devido a essa importância, é muito importante que as regras de arredondamento sejam muito bem entendidas para que não haja inconsistências quando o arrendondamento for aplicado nos números, especialmente em grandes volumes de montantes.
Para atender a essas necessidades, o SQL Server nos disponibiliza 3 funções para arredondamento:
- FLOOR: Retorna um número inteiro, arredondado sempre para baixo, ou seja, retorna a parte inteira do número decimal informado
- CEILING: Retorna um valor inteiro, arredondando sempre para cima, ou seja, a parte inteira + 1 do número decimal informado (caso o valor decimal seja > 0)
- ROUND: Retorna um valor decimal, arredondando de acordo com o número de casas decimais especificadas na função. Por padrão, caso a casa decimal seja <= 4, será arredondado para baixo. Caso a casa decimal seja>= 5, será arredondado para cima.
Esse comportamento pode ser alterado pelo 3º parâmetro da função, que, quando informado o valor 0, vai realizar o truncamento dos dados ao invés de arredondar, ou seja, 10.999 com 2 casas decimais ficaria 10.99.
Entretanto, quando temos um sistema que precisa seguir a norma ABNT NBR 5891, temos um problema, pois as funções acima não atendem ao critério da norma, que é definida da seguinte forma:
Regras de arredondamento
As regras de arredondamento, seguindo a Norma ABNT NBR 5891, aplicam-se aos algarismos decimais situados na posição seguinte ao número de algarismos decimais que se queira transformar, ou seja, se tivermos um número de 4, 5, 6, n algarismos decimais e quisermos arredondar para 2, aplicar-se-ão estas regras de arredondamento:
Se os algarismos decimais seguintes forem menores que 50, 500, 5000…, o anterior não se modifica.
Se os algarismos decimais seguintes forem maiores a 50, 500, 5000…, o anterior incrementa-se em uma unidade.
Se os algarismos decimais seguintes forem iguais a 50, 500, 5000…, verifica-se o anterior; se for par, o anterior não se modifica; se for impar, o anterior incrementa-se em uma unidade.
Exemplos
Arredondando a 2 algarismos decimais deveremos ter em atenção o terceiro e quarto decimal. Assim, conforme as regras anteriores:
O número 12,6529 seria arredondado para 12,65 (aqui fica 12.65, uma vez que 29 é inferior a 50, então não se modifica)
O número 12,86512 seria arredondado para 12,87 (aqui fica 12.87, uma vez que 512 é superior a 500, então incrementa-se uma unidade)
O número 12,744623 seria arredondado para 12,74 (aqui fica 12.74, uma vez que 4623 é inferior a 5000, então não se modifica)
O número 12,8752 seria arredondado para 12,88 (aqui fica 12.88, uma vez que 52 é superior a 50, então incrementa-se uma unidade)
O número 12,8150 seria arredondado para 12,82 (aqui fica 12.82, uma vez que os algarismos seguintes é igual a 50 e o anterior é impar,nesse caso 1, então incrementa-se uma unidade)
O número 12,8050 seria arredondado para 12,80 (aqui fica 12.80, uma vez que os algarismos seguintes é igual a 50 e o anterior é par, nesse caso 0, então o anterior não se modifica)
O numero 13,4666…, se fossemos arredondar à parte inteira, será sempre arredondado para 13, pois 4666… sempre será menor que 5000… (Se fizermos o arredondamento número a número, teríamos : 13,4666… → 13,47 → 13,5 → 14, porém, isso seria afirmar que 13,4666… está mais próximo de 14 do que está de 13, que não é verdade. Portanto, não devemos arredondar o número já previamente arredondado!!!)
Referência: https://pt.wikipedia.org/wiki/Arredondamento
Arredondamento de números com FLOOR, ROUND e CEILING
Conforme citado na introdução desse artigo, o SQL Server nos disponibiliza 3 funções para arredondamento:
- FLOOR: Retorna um número inteiro, arredondado sempre para baixo, ou seja, retorna a parte inteira do número decimal informado
- CEILING: Retorna um valor inteiro, arredondando sempre para cima, ou seja, a parte inteira + 1 do número decimal informado (caso o valor decimal seja > 0)
- ROUND: Retorna um valor decimal, arredondando de acordo com o número de casas decimais especificadas na função. Por padrão, caso a casa decimal seja <= 4, será arredondado para baixo. Caso a casa decimal seja>= 5, será arredondado para cima.
Esse comportamento pode ser alterado pelo 3º parâmetro da função, que, quando informado o valor 0, vai realizar o truncamento dos dados ao invés de arredondar, ou seja, 10.999 com 2 casas decimais ficaria 10.99.
Vou demonstrar alguns exemplos para facilitar o entendimento da diferença dessas funções:
1 2 3 4 |
SELECT CAST(CEILING(10.4925) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.4925) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.4925, 2) AS [Round] |
1 2 3 4 |
SELECT CAST(CEILING(10.0001) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.0001) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.0001, 2) AS [Round] |
1 2 3 4 |
SELECT CAST(CEILING(10.5000) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.5000) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.5000, 2) AS [Round] |
Nesse exemplo, vamos utilizar o 3º parâmetro da função ROUND() para forçar o arredondamento para cima (0 = arredondamento padrão, ou seja, 0 a 4 arredonda pra baixo e 5 a 9 arredonda pra cima) e o truncamento (1 = Truncamento, ou seja, não arredonda, simplesmente corta as casas decimais acima do limite especificado na função).
1 2 3 4 5 6 |
SELECT CAST(CEILING(10.9999) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.9999) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.9999, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.9999, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.9999, 2, 0)) AS Round_Up |
1 2 3 4 5 6 |
SELECT CAST(CEILING(10.235) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.235) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.235, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.235, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.235, 2, 0)) AS Round_Up |
1 2 3 4 5 6 |
SELECT CAST(CEILING(10.225) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(10.225) AS NUMERIC(18, 2)) AS [Floor], ROUND(10.225, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(10.225, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(10.225, 2, 0)) AS Round_Up |
Como vocês puderam perceber, a função ROUND() atende 2 das 3 regras definidas na norma ABNT, mas no exemplo do valor 10.225, segundo a norma, o arredondamento deveria ter sido feito para 10,22 e não 10,23.
Arredondamento de números seguindo a norma ABNT NBR 5891
Como vimos nos exemplos anteriores, quando caímos na regra “Se os algarismos decimais seguintes forem iguais a 50, 500, 5000…, verifica-se o anterior; se for par, o anterior não se modifica; se for impar, o anterior incrementa-se em uma unidade.”, as funções de arredondamento padrão do SQL Server acabam não atendendo a essa necessidade.
Para isso, vou disponibilizar a função abaixo, que consegue cumprir totalmente as definições de arredondamento da norma ABNT NBR 5891:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
CREATE FUNCTION dbo.fncArredondamento_ABNT ( @Valor NUMERIC(38, 16) ) RETURNS NUMERIC(38, 16) AS BEGIN DECLARE @Parte_Inteira INT = ROUND(@Valor, 0, 1), @Parte_Decimal NUMERIC(38, 16) = @Valor - ROUND(@Valor, 0, 1), @Nova_Parte_Decimal NUMERIC(38, 16), @SegundoDecimal INT, @DoisPrimeirosDecimais AS NUMERIC(18, 2), @RestanteDosDecimais AS NUMERIC(38, 16) SELECT @SegundoDecimal = SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 4, 1), @DoisPrimeirosDecimais = '0.' + SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 3, 2), @RestanteDosDecimais = '0.' + SUBSTRING(CAST(@Parte_Decimal AS VARCHAR(40)), 5, 16) SELECT @Nova_Parte_Decimal = (CASE WHEN @RestanteDosDecimais > 0.5 THEN @DoisPrimeirosDecimais + 0.01 WHEN @RestanteDosDecimais < 0.5 THEN @DoisPrimeirosDecimais ELSE @DoisPrimeirosDecimais + IIF(@SegundoDecimal % 2 = 0, 0.00, 0.01) END) RETURN (@Parte_Inteira + @Nova_Parte_Decimal) END |
Exemplos de uso
O número 12,6529 seria arredondado para 12,65 (aqui fica 12.65, uma vez que 29 é inferior a 50, então não se modifica)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.6529) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.6529) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.6529, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.6529, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.6529, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.6529) AS [Round_ABNT] |
O número 12,86512 seria arredondado para 12,87 (aqui fica 12.87, uma vez que 512 é superior a 500, então incrementa-se uma unidade)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.86512) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.86512) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.86512, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.86512, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.86512, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.86512) AS [Round_ABNT] |
O número 12,744623 seria arredondado para 12,74 (aqui fica 12.74, uma vez que 4623 é inferior a 5000, então não se modifica)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.744623) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.744623) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.744623, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.744623, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.744623, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.744623) AS [Round_ABNT] |
O número 12,8752 seria arredondado para 12,88 (aqui fica 12.88, uma vez que 52 é superior a 50, então incrementa-se uma unidade)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.8752) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8752) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8752, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8752, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8752, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8752) AS [Round_ABNT] |
O número 12,8150 seria arredondado para 12,82 (aqui fica 12.82, uma vez que os algarismos seguintes é igual a 50 e o anterior é impar, nesse caso 1, então incrementa-se uma unidade)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.8150) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8150) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8150, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8150, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8150, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8150) AS [Round_ABNT] |
O número 12,8050 seria arredondado para 12,80 (aqui fica 12.80, uma vez que os algarismos seguintes é igual a 50 e o anterior é par, nesse caso 0, então o anterior não se modifica)
1 2 3 4 5 6 7 |
SELECT CAST(CEILING(12.8050) AS NUMERIC(18, 2)) AS [Ceiling], CAST(FLOOR(12.8050) AS NUMERIC(18, 2)) AS [Floor], ROUND(12.8050, 2) AS [Round], CONVERT(DECIMAL(10,2), ROUND(12.8050, 2, 1)) AS Round_Down, CONVERT(DECIMAL(10,2), ROUND(12.8050, 2, 0)) AS Round_Up, dbo.fncArredondamento_ABNT(12.8050) AS [Round_ABNT] |
Bom, é isso aí, pessoal!
Espero que tenham gostado desse post e até a próxima!
Dirceu, seu material sempre é de primeira, parabéns!
Uma dúvida. Qual sua sugestão para parametrizar a quantidade de decimais na função ABNT?
Por exemplo, em alguns lugares deve arredondar com 2 decimais, outros locais arredonda com 4, e assim por diante.
Muito obrigado!
muito bom!
nesta fn já está incluída a revisão de 2014 a abnt?
muito bom!!! parabens!!!
Obrigado 🙂