Ir para conteúdo
  • Cadastre-se

dev botao

Minha Pequena Contribuição: Arredondamento Abnt


Ver Solução Respondido por Sommus,
  • Este tópico foi criado há 1409 dias atrás.
  • Talvez seja melhor você criar um NOVO TÓPICO do que postar uma resposta aqui.

Recommended Posts

Pessoal,

 

A quem se interessar, seque abaixo função para realizar o arredondamento ABNT (Arredondamento utilizado no ECF)

 

Escrevi na linguagem Harbour, mas, basta apenas trocar os comandos para converter para qualquer outra linguagem (Se precisarem de ajuda na conversão, é só postar aqui, que tentarei ajudar)

 

FUNCTION Round_ABNT(nValor,nDecimais)


   LOCAL nRetorno:=nValor, cDecimais:=SubStr(Str(nValor),At('.',Str(nValor))+1), nSubsequente:=nDecimais+1
   
   if nDecimais<1
      RETURN Int(nRetorno)
   endif


   if Len(cDecimais) <= nDecimais
      RETURN nRetorno
   endif
   
   if SubStr(cDecimais,nSubsequente,1)>'5' .or. SubStr(cDecimais,nSubsequente,1)<'5' //Se a casa decimal SUBSEQUENTE for DIFERENTE de 5
      nRetorno:=Round(nValor,nDecimais)                                              //ARREDONDA
    elseif SubStr(cDecimais,nSubsequente,1)=='5'                                     //Se a casa decimal SUBSEQUENTE for IGUAL a 5
      if Mod(Val(SubStr(cDecimais,nDecimais,1)),2) <> 0                              //Se a casa decimal que será CONSERVADA, for IMPAR
         nRetorno:=Round(nValor,nDecimais)                                           //ARREDONDA
       else                                                                          //se a casa decimal que será CONSERVADA, for PAR
         if Val(SubStr(cDecimais,nSubsequente+1,1)) > 0                                //Se APÓS a casa decimal SUBSEQUENTE, houver ALGUM algarismo MAIOR que ZERO
            nRetorno:=Round(nValor,nDecimais)                                        //ARREDONDA
          else                                                                       //Se APÓS a casa decimal SUBSEQUENTE, não houver NENHUM outro algarismo ou TODOS forem iguais a ZERO
            nRetorno:=Truncate(nValor,nDecimais)                                     //TRUNCA (Esse é o único momento em que o "arredondamento ABNT" se diferencia do "arredondamento normal")
         endif
      endif
   endif
   
RETURN nRetorno




FUNCTION Truncate(nValor,nDecimais)


   LOCAL nRetorno:=nValor, cDecimais:=SubStr(Str(nValor),At('.',Str(nValor))+1)
   
   if nDecimais<1
      RETURN Int(nRetorno)
   endif
   
   if Len(cDecimais) <= nDecimais
      RETURN nRetorno
   endif
   
   nRetorno:=Val( Str(Int(nValor))+'.'+SubStr(cDecimais,1,nDecimais) )


RETURN nRetorno

 
 

Nos vários testes que fiz aqui, funcionou muito bem, se encontrarem algum bug, favor, reportar aqui... Obrigado!

 

Espero ter contribuído,

 

Abraços,

 

Reginaldo

Editado por Sommus Sistemas
  • Curtir 4
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • Consultores

Para quem usa os componentes, o ACBrUtil tem essa função como

  • Curtir 1

[]'s

Consultor SAC ACBr

Elton
Profissionalize o ACBr na sua empresa, conheça o ACBr Pro.

Projeto ACBr     Telefone:(15) 2105-0750 WhatsApp(15)99790-2976.

Um engenheiro de Controle de Qualidade(QA) entra num bar. Pede uma cerveja. Pede zero cervejas.
Pede 99999999 cervejas. Pede -1 cervejas. Pede um jacaré. Pede asdfdhklçkh.
Link para o comentário
Compartilhar em outros sites

  • 1 ano depois...

Opa, muito obrigado pelo código base, man!!

 

Me serviu muito, passei ele para JavaScript.

Segue abaixo o que eu fiz, se alguém usar e encontrar algum bug, por favor, reporte para arrumar-mos!

 

function round_abnt(nValor, nDecimais) {

	var nRetorno = nValor;
	spl = nValor.toString().split(".");
	var cDecimais = spl[1];
	var nSubsequente = nDecimais;

	if (nDecimais < 1) {
		return parseInt(nRetorno);
	}
	
	if (cDecimais.length <= nDecimais) {
		return parseFloat(nRetorno);
	}
	
	//Se a casa decimal SUBSEQUENTE for DIFERENTE de 5
	if (cDecimais.substr(nSubsequente,1) > '5' ||  cDecimais.substr(nSubsequente,1) < '5') {
		nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
	}
	//Se a casa decimal SUBSEQUENTE for IGUAL a 5
	else if (cDecimais.substr(nSubsequente, 1) == '5') { 
	
		//Se a casa decimal que será CONSERVADA, for IMPAR
		if ((cDecimais.substr(nDecimais-1, 1) % 2) != 0) {
			nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
		}
		//Se a casa decimal que será CONSERVADA, for PAR
		else 
		//Se APÓS a casa decimal SUBSEQUENTE, houver ALGUM algarismo MAIOR que ZERO
		if ( cDecimais.substr(nSubsequente+1, 1) > 0 ) {
			nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
		}
		//Se APÓS a casa decimal SUBSEQUENTE, não houver NENHUM outro algarismo ou TODOS forem iguais a ZERO
		else {
			//TRUNCA (Esse é o único momento em que o "arredondamento ABNT" se diferencia do "arredondamento normal")
			nRetorno = Truncate(nValor, nDecimais);
		}
	}
	return parseFloat(nRetorno);
}

function Truncate(nValor, nDecimais) {

	var nRetorno = nValor;
	spl = nValor.toString().split(".");
	var cDecimais = spl[1];

	if (nDecimais < 1) {
		return parseInt(nRetorno);
	}

	if (cDecimais.length <= nDecimais) {
		return nRetorno;
	}

	//Pega a parte inteira do número e concatena com a substring sem alterar, pois é PAR e vai manter!
	nRetorno = parseInt(nValor.toString()) + '.' + cDecimais.substr(0, nDecimais);
	nRetorno = parseFloat(nRetorno);

	return nRetorno;
}
:D :D Editado por Felipe Benutti
  • Curtir 1
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • Solution

Pessoal,

 

Precisei do código também para MySQL e fiz a conversão, então, segue abaixo:

CREATE FUNCTION ROUND_ABNT(nValor real, nDecimais int) RETURNS real 
BEGIN 
   DECLARE nRetorno real; 
   DECLARE cDecimais char(100); 
   DECLARE nSubsequente int; 
   
   SET nRetorno = nValor; 
   SET cDecimais = SUBSTRING(CONVERTE(nValor),INSTR(CONVERTE(nValor),'.')+1); 
   SET nSubsequente = nDecimais+1; 
   
   IF (nDecimais<1) THEN 
      RETURN TRUNCATE(nRetorno,0); 
   END IF; 
   
   IF (LENGTH(cDecimais) <= nDecimais) THEN 
      RETURN nRetorno; 
   END IF; 
   
   IF (SUBSTRING(cDecimais,nSubsequente,1)>'5' OR SUBSTRING(cDecimais,nSubsequente,1)<'5') THEN 
      SET nRetorno = ROUND(nValor,nDecimais); 
   ELSEIF (SUBSTRING(cDecimais,nSubsequente,1)='5') THEN 
      IF (MOD(SUBSTRING(cDecimais,nDecimais,1),2) <> 0) THEN 
         SET nRetorno = ROUND(nValor,nDecimais); 
      ELSE 
         IF (SUBSTRING(cDecimais,nSubsequente+1,1) > 0) THEN 
            SET nRetorno = ROUND(nValor,nDecimais); 
         ELSE 
            SET nRetorno = TRUNCATE(nValor,nDecimais); 
         END IF; 
      END IF; 
   END IF; 
   
   RETURN nRetorno; 
END; 

Editado por Sommus Sistemas
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • 11 meses depois ...

e aqui vai minha contribuição para o firebird 3.0

create or alter function ROUNDABNT (
    AVALOR double precision,
    ADECIMAIS smallint)
returns double precision
AS
declare variable cDecimais varchar(100);
declare variable vlrstr varchar(100);
declare variable nSubsequente smallint;
declare variable posponto smallint;
BEGIN
 vlrstr = Cast(AVALOR as varchar(100));
 posponto = POSITION('.',vlrstr);
 cDecimais = SUBSTRING(vlrstr from posponto+1 for CHAR_LENGTH(vlrstr));
 nSubsequente = ADECIMAIS+1;
 if (:ADECIMAIS < 1) Then
  RETURN TRUNC(AVALOR);
 else
 If (CHAR_LENGTH(cDecimais) <= :ADECIMAIS) Then
  RETURN AVALOR;
 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(AVALOR,ADECIMAIS);
   Else
   if (Cast(SUBSTRING(cDecimais From nSubsequente For 1)as double precision) = 5) Then
    If (MOD(Cast(SUBSTRING(cDecimais From ADECIMAIS For 1)as double precision) ,2) <> 0) Then
     RETURN ROUND(AVALOR,ADECIMAIS);
   Else
   If (Cast(SUBSTRING(cDecimais From nSubsequente+1 For 1)as double precision) > 0) Then
    RETURN ROUND(AVALOR,ADECIMAIS);
   Else
    RETURN TRUNC(AVALOR,ADECIMAIS);
  End
END

 

  • Curtir 2
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • 2 meses depois ...
  • 1 mês depois ...
Em 12/03/2016 at 07:42, datilas disse:

e aqui vai minha contribuição para o firebird 3.0


create or alter function ROUNDABNT (
    AVALOR double precision,
    ADECIMAIS smallint)
returns double precision
AS
declare variable cDecimais varchar(100);
declare variable vlrstr varchar(100);
declare variable nSubsequente smallint;
declare variable posponto smallint;
BEGIN
 vlrstr = Cast(AVALOR as varchar(100));
 posponto = POSITION('.',vlrstr);
 cDecimais = SUBSTRING(vlrstr from posponto+1 for CHAR_LENGTH(vlrstr));
 nSubsequente = ADECIMAIS+1;
 if (:ADECIMAIS < 1) Then
  RETURN TRUNC(AVALOR);
 else
 If (CHAR_LENGTH(cDecimais) <= :ADECIMAIS) Then
  RETURN AVALOR;
 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(AVALOR,ADECIMAIS);
   Else
   if (Cast(SUBSTRING(cDecimais From nSubsequente For 1)as double precision) = 5) Then
    If (MOD(Cast(SUBSTRING(cDecimais From ADECIMAIS For 1)as double precision) ,2) <> 0) Then
     RETURN ROUND(AVALOR,ADECIMAIS);
   Else
   If (Cast(SUBSTRING(cDecimais From nSubsequente+1 For 1)as double precision) > 0) Then
    RETURN ROUND(AVALOR,ADECIMAIS);
   Else
    RETURN TRUNC(AVALOR,ADECIMAIS);
  End
END

 

Nossa, Obrigado Datilas!

como aqui usamos firebird 2.5, converti sua função em procedure, segue a contribuição tb:
 

CREATE OR ALTER PROCEDURE SP_UDF_ROUNDABNT (
    AVALOR DOUBLE PRECISION,
    ADECIMAIS SMALLINT)
RETURNS
  (
   VALOR DOUBLE PRECISION
  )
AS
DECLARE VARIABLE CDECIMAIS VARCHAR(100);
DECLARE VARIABLE VLRSTR VARCHAR(100);
DECLARE VARIABLE NSUBSEQUENTE SMALLINT;
DECLARE VARIABLE POSPONTO SMALLINT;
BEGIN
     VLRSTR = CAST(AVALOR AS VARCHAR(100));
     POSPONTO = POSITION('.',VLRSTR);
     CDECIMAIS = SUBSTRING(VLRSTR FROM POSPONTO+1 FOR CHAR_LENGTH(VLRSTR));
     NSUBSEQUENTE = ADECIMAIS+1;
     IF (:ADECIMAIS < 1) THEN
     BEGIN
        VALOR = TRUNC(AVALOR);
        SUSPEND;
        exit;
     END
     ELSE
     IF (CHAR_LENGTH(CDECIMAIS) <= :ADECIMAIS) THEN
     BEGIN
        VALOR = AVALOR;
        SUSPEND;
        exit;
     END
     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
       BEGIN
           VALOR = ROUND(AVALOR,ADECIMAIS);
           SUSPEND;
           exit;
       END
       ELSE
       IF (CAST(SUBSTRING(CDECIMAIS FROM NSUBSEQUENTE FOR 1)AS DOUBLE PRECISION) = 5) THEN
       begin
               IF (MOD(CAST(SUBSTRING(CDECIMAIS FROM ADECIMAIS FOR 1)AS DOUBLE PRECISION) ,2) <> 0) THEN
               BEGIN
                  VALOR = ROUND(AVALOR,ADECIMAIS);
                  SUSPEND;
                  exit;
               END
               ELSE IF (CAST(SUBSTRING(CDECIMAIS FROM NSUBSEQUENTE+1 FOR 1)AS DOUBLE PRECISION) > 0) THEN
               BEGIN
                  VALOR = ROUND(AVALOR,ADECIMAIS);
                  SUSPEND;
                  exit;
               END
               ELSE
               begin
                   VALOR = TRUNC(AVALOR,ADECIMAIS);
                   SUSPEND;
                   exit;
               END
       end
    END
END

Abraço!

Editado por wesleyblanco
  • Curtir 1
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • 2 meses depois ...

Obrigado pela lógica pessoal. Segue o código em C++

#include<math.h>
#include<iostream>

double roundtoabnt(double value, int digits)
{
    double potencia, valorElevado, restante;
    double parteInteira, parteFracionada, auxiliar;
    int ultimoDigitoMantido;

    potencia = pow(10, abs(digits));
    valorElevado = value * (potencia);

    parteInteira = trunc(valorElevado);
    parteFracionada = trunc(modf(valorElevado, &auxiliar) * potencia);

    if (parteFracionada > 50) {
        parteInteira++;
    } else if (parteFracionada == 50) {

        ultimoDigitoMantido = round((modf(parteInteira / 10, &auxiliar)) * 10);

        if (ultimoDigitoMantido % 2) {
            parteInteira++;
        } else {
            restante = (modf(valorElevado * 10, &auxiliar));
        }
    }

    return (parteInteira / potencia);
}

 

  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • 8 meses depois ...
Em 30/03/2015 at 12:04, Felipe Benutti disse:

Opa, muito obrigado pelo código base, man!!

 

Me serviu muito, passei ele para JavaScript.

Segue abaixo o que eu fiz, se alguém usar e encontrar algum bug, por favor, reporte para arrumar-mos!

 

 


function round_abnt(nValor, nDecimais) {

	var nRetorno = nValor;
	spl = nValor.toString().split(".");
	var cDecimais = spl[1];
	var nSubsequente = nDecimais;

	if (nDecimais < 1) {
		return parseInt(nRetorno);
	}
	
	if (cDecimais.length <= nDecimais) {
		return parseFloat(nRetorno);
	}
	
	//Se a casa decimal SUBSEQUENTE for DIFERENTE de 5
	if (cDecimais.substr(nSubsequente,1) > '5' ||  cDecimais.substr(nSubsequente,1) < '5') {
		nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
	}
	//Se a casa decimal SUBSEQUENTE for IGUAL a 5
	else if (cDecimais.substr(nSubsequente, 1) == '5') { 
	
		//Se a casa decimal que será CONSERVADA, for IMPAR
		if ((cDecimais.substr(nDecimais-1, 1) % 2) != 0) {
			nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
		}
		//Se a casa decimal que será CONSERVADA, for PAR
		else 
		//Se APÓS a casa decimal SUBSEQUENTE, houver ALGUM algarismo MAIOR que ZERO
		if ( cDecimais.substr(nSubsequente+1, 1) > 0 ) {
			nRetorno = nRetorno.toFixed(2); //Math.round((nRetorno + 0.00001) * 100) / 100; //ARREDONDA
		}
		//Se APÓS a casa decimal SUBSEQUENTE, não houver NENHUM outro algarismo ou TODOS forem iguais a ZERO
		else {
			//TRUNCA (Esse é o único momento em que o "arredondamento ABNT" se diferencia do "arredondamento normal")
			nRetorno = Truncate(nValor, nDecimais);
		}
	}
	return parseFloat(nRetorno);
}

function Truncate(nValor, nDecimais) {

	var nRetorno = nValor;
	spl = nValor.toString().split(".");
	var cDecimais = spl[1];

	if (nDecimais < 1) {
		return parseInt(nRetorno);
	}

	if (cDecimais.length <= nDecimais) {
		return nRetorno;
	}

	//Pega a parte inteira do número e concatena com a substring sem alterar, pois é PAR e vai manter!
	nRetorno = parseInt(nValor.toString()) + '.' + cDecimais.substr(0, nDecimais);
	nRetorno = parseFloat(nRetorno);

	return nRetorno;
}

:D:D

Olá,
estive testando o código e vi que ele apenas se comporta corretamente quando usa 2 decimais, por estar fixo:
"nRetorno = nRetorno.toFixed(2);"
quando deveria ser:
"nRetorno = nRetorno.toFixed(nDecimais);"
 

Link para o comentário
Compartilhar em outros sites

  • 3 anos depois...

Para o pessoal que precisar fazer este arredondamento usando c#, segue o método, já com testes usando Xunit:

 

using System;
using Xunit;

namespace XUnitTestProject1
{
    public class ArredondamentoTest
    {
        [Theory]
        [InlineData(0.342, 0.34)]
        [InlineData(0.346, 0.35)]
        [InlineData(0.3452, 0.35)]
        [InlineData(0.3450, 0.34)]
        [InlineData(0.332, 0.33)]
        [InlineData(0.336, 0.34)]
        [InlineData(0.3352, 0.34)]
        [InlineData(0.3350, 0.34)]
        [InlineData(0.3050, 0.30)]
        [InlineData(0.3150, 0.32)]
        public void TestRoundAbnt5891(decimal valorOriginal, decimal arredondadoEsperado)
        {
            decimal arredondadoMetodoABNT = RoundAbnt5891(valorOriginal, 2);
            Assert.Equal(arredondadoEsperado, arredondadoMetodoABNT);
        }

        /// <summary>
        /// Método de arredondamento "round-half-even"
        /// Ou "arredondamento do banqueiro"
        /// </summary>
        /// <param name="value"></param>
        /// <param name="digits"></param>
        /// <returns></returns>
        private decimal RoundAbnt5891(decimal value, int digits)
        {
            return Math.Round(value, digits, MidpointRounding.ToEven);
        }
    }
}

 

  • Curtir 1
  • Obrigado 1
Link para o comentário
Compartilhar em outros sites

  • Este tópico foi criado há 1409 dias atrás.
  • Talvez seja melhor você criar um NOVO TÓPICO do que postar uma resposta aqui.

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora
×
×
  • Criar Novo...

Informação Importante

Colocamos cookies em seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies, caso contrário, assumiremos que você está bem para continuar.