quinta-feira, 5 de abril de 2012

Java, valores monetários: float, double e BigDecimal

Quando estamos trabalhando com valores monetários, os tipos float e double não são adequados devido aos problemas de precisão. Nem todas as frações tem representação exata nestes dois tipos de dados. Para ver isso na prática, basta fazer o seguinte teste:



Altere para float e teste. E terá algo assim: saida = soma 10.000002.

Quando estamos trabalhando com valores monetários precisamos definir as seguintes regras de negócio:
 1. Escala - número de casas decimais após a vírgula.
 2. Arredondamento - como vamos tratar os valores após a escala definida em 1.
 3. Quando arredondar - em que momento vamos fazer o arredondamento.

 Para definir essas regras, temos que verificar diversas leis ou normas: fiscais, bancárias, contábeis, legais, etc.

 Como float e double não servem temos duas alternativas: ou usar inteiros e controlar o número decimais por conta própria ou usar a classe BigDecimal.

 Vamos ver a classe BigDecimal.

 Para definir: BigDecimal bd = new BigDecimal("1.01");
                      BigDecimal bd2 = new BigDecimal("2.00");
 Para somar: bd = bd.add(bd2);
 Para subtrair: bd = bd.subtract(bd2);
 Para multiplicar: bd = bd.multiply(bd2);
 Para dividir: bd = bd.divide(bd2, intScala, BigDecimal.ROUND_UP);

 Dicas:
   a. Definição
       Defina sempre a partir de uma string.
       Para converter de float use: Float.toString(meuFloat);
       Para converter de double use: Double.toString(meuDouble);
   b. Comparação
       Use os métodos: compareTo() ou signum() - retorna inteiro (-1 para menor, 0 para == e 1 para maior)
       Não use equals() - ele é sensitivo a escala, vai dar diferente se a escala for diferente, mesmo para números iguais.
   c. BigDecimal é um objeto imutável
       Então após cada operação ele retorna um novo objeto - não se esqueça de salvá-lo!

 O BigDecimal tem oito formas de arredondamento.
 Segue a lista. Está em inglês, em baixo eu tento decifrar e dou uns exemplos, sempre para a escala 2.

"ROUND_UP " Rounding mode to round away from zero.
        Arredonda para: o mais distante de zero, maior em valor absoluto
        Se valor negativo: arredonda para o menor valor
        Se valor positivo: arredonda para o maior valor
        -0.001 => -0.01
          0.001 => 0.01

"ROUND_DOWN " Rounding mode to round towards zero.
         Arredonda para: o mais próximo de zero, menor em valor absoluto
         Se valor negativo: arredonda para o maior valor
         Se valor positivo: arredonda para o menor valor
        -0.009 => 0.00
         0.009 => 0.00

"ROUND_CEILING " Rounding mode to round towards positive infinity.
       Arredonda para: o maior valor
      -0.001 => 0.00
       0.001 => 0.01

"ROUND_FLOOR " Rounding mode to round towards negative infinity.
      Arredonda para: o menor valor
     -0.001 => -0.01
      0.001 => 0.00

"ROUND_HALF_DOWN " Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
      Verifica o valor excedente: se diferente de 5, para o "vizinho mais próximo"
             se igual a 5 => para o valor absoluto menor
      -0.006 => -0.01
      -0.005 =>  0.00
      -0.004 =>  0.00
        0.004 => 0.00
        0.005 => 0.00
        0.006 => 0.01

"ROUND_HALF_UP " Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
      Verifica o valor excedente: se diferente de 5, para o "vizinho mais próximo"
            se igual a 5 => para o valor absoluto maior
      -0.006 => -0.01
      -0.005 => -0.01
      -0.004 => 0.00
       0.004 => 0.00
       0.005 => 0.01
       0.006 => 0.01

"ROUND_HALF_EVEN " Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.
      Verifica o valor excedente: se diferente de 5, para o "vizinho mais próximo"
          se igual a 5 => verifica o digito a ser arredondado
              se impar => arredonda para o maior em valor absoluto
              se par     => arredonda para o menor em valor absoluto
 Esse tipo de arredondamento é indicado para evitar distorção no resultado devido aos arredondamentos cumulativos na mesma direção. Um uso seria em estatísticas, onde um grande volume de dados são manipulados.
     -0.015 => -0.02
     -0.025 => -0.02
      0.015 => 0.02
      0.025 => 0.02

"ROUND_UNNECESSARY" Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.
     Quando não tem arredondamento a ser efetuado. Se tiver ocorre um ArithmeticException.
    -0.01 => -0.01
     0.01 => 0.01
     0.011 => ArithmeticException

 BigDecimal bd = new BigDecimal("10.001");
 bd = bd.setScale(2, BigDecimal.ROUND_UP); // = 10.01

BigAbraços!

5 comentários:

  1. Depois de 2 anos não posta nada! Amanha vai ser 11 de setembro..14 anos depois da queda das torre gemeas!

    ResponderExcluir
  2. E hoje que é 6 de novembro 2016? Mas ainda está ajudando... Valeu

    ResponderExcluir
  3. hoje 15 de novembro de 2017, e continua ajudando....Obrigado!!!

    ResponderExcluir