Arredondamento do tipo ABNT em itens de serviço NFs

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:

https://documentacao.senior.com.br/goup/5.10.1/regra_funcoes/arredonda-abnt.htm#:~:text=A%20regra%20de%20arredondamento%20da%20ABNT%20prev%C3%AA%20os%20seguintes%20arredondamentos,torna%2Dse%204%2C30.