Vez ou outra somos obrigados a usar a matemática de um jeito diferente, os matemáticos e pesquisadores tratam arredondamentos de uma maneira cientifica enquanto o mundo civil pode trabalhar de uma maneira diferente, este é o caso da norma ABNT para arredondamento de preços para itens de serviço, elas funcionam do seguinte modo:
1. Quando o algarismo a ser conservado for seguido de algarismo inferior a 5, o algarismo a ser conservado permanece sem alteração. Exemplo: 4,303 arredondado em duas casas decimais torna-se 4,30. Fala o teste:
-- O arredondamento psql concorda com a regra ABNT retornando 4.3 select cast(4.303 as numeric(18,2)) from rdb$database
2. Quando o algarismo a ser conservado for seguido de algarismo superior a 5, ou igual a 5 seguindo de um algarismo diferente de zero, soma-se uma unidade ao algarismo a ser conservado. Exemplo: 15,4875 arredondado em duas casas decimais torna-se 15,49.
-- o arredondamento psql concorda com a regra ABNT retornando 15.49 select cast(15.4875 as numeric(18,2)) from rdb$database
3. Quando o algarismo a ser conservado for ímpar, seguido de 5 e posteriormente de zeros, soma-se uma unidade ao algarismo a ser conservado. Exemplo: 25,7750 arredondado em duas casas decimais fica 25,78.
-- O arredondamento psql concorda com a regra ABNT retornando 25.78 select cast(25.7750 as numeric(18,2)) from rdb$database
4. Quando o algarismo a ser conservado for par, seguido de 5 e posteriormente de zeros, o algarismo a ser conservado permanece sem alteração. Exemplo: 31,7250 arredondado em duas casas decimais fica 31,72.
-- No exemplo abaixo, o arredondamento psql FALHA COM A REGRA ABNT -- retornando 31,73 ao invés de 31.72 segundo a regra ABNT select cast(31.7250 as numeric(18,2)) from rdb$database
Então é apenas no ponto “4” que o psql passa a falhar segundo esta norma. É claro que arredondar desse jeito é um pouco de preciosismo, mas ao rigor da lei e da norma ABNT para itens de prestação de serviço em NFs é preciso fazê-lo. Então como ? A SQL Function abaixo aplica a regra ABNT que desejamos:
create or alter function GET_ROUND_AS_ABNT ( P_VALOR double precision, P_DECIMAIS smallint) returns double precision as declare variable CDECIMAIS varchar(100); declare variable STR_VALOR varchar(100); declare variable NSUBSEQUENTE smallint; declare variable POS_DEC smallint; -- constante para indicar separador de decimal, -- mude para virgula se for o seu caso. declare variable C_CHAR_DEC varchar(1)='.'; BEGIN STR_VALOR = CAST(P_VALOR AS VARCHAR(100)); POS_DEC = POSITION(C_CHAR_DEC,STR_VALOR); cDecimais = SUBSTRING(STR_VALOR FROM POS_DEC+1 FOR CHAR_LENGTH(STR_VALOR)); nSubsequente = P_DECIMAIS+1; IF (:P_DECIMAIS < 1) THEN RETURN TRUNC(P_VALOR); ELSE IF (CHAR_LENGTH(cDecimais) <= :P_DECIMAIS) THEN RETURN P_VALOR; ELSE BEGIN IF ((CAST(SUBSTRING(cDecimais FROM nSubsequente FOR 1) AS INTEGER) > 5) OR (CAST(SUBSTRING(cDecimais FROM nSubsequente FOR 1)AS DOUBLE PRECISION) < 5)) THEN RETURN ROUND(P_VALOR,P_DECIMAIS); ELSE IF (CAST(SUBSTRING(cDecimais FROM nSubsequente FOR 1)AS DOUBLE PRECISION) = 5) THEN IF (MOD(CAST(SUBSTRING(cDecimais FROM P_DECIMAIS FOR 1)AS DOUBLE PRECISION) ,2) <> 0) THEN RETURN ROUND(P_VALOR,P_DECIMAIS); ELSE IF (CAST(SUBSTRING(cDecimais FROM nSubsequente+1 FOR 1)AS DOUBLE PRECISION) > 0) THEN RETURN ROUND(P_VALOR,P_DECIMAIS); ELSE RETURN TRUNC(P_VALOR,P_DECIMAIS); END END
Este código não é de minha autoria, quando iria criar uma SQL Function para resolver tal problema de arredondamento ABNT, fiz o que sempre faço, procuro na internet para saber se alguém já não fez isso antes e encontrei as informações necessárias para este código que modifiquei levemente. Exemplo de uso:
select GET_ROUND_AS_ABNT(31.7250, 2) from rdb$database
Conclusão
Lembre-se de que este é um tipo de arredondamento especial, só é usado nas situações de arredondamento de itens de serviço em NF-s, portanto descarte o uso dessa função para outras operações.
O material de referencia desse artigo foi obtido em: