Trabalhar com sistema que manipulam valores monetários é uma tarefa crítica e que deve exigir o máximo de atenção do desenvolvedor. Como exemplo, temos sistemas de Caixa de Lojas, Supermercados, Sistemas Bancários e etc.

Um cálculo errado em um Sistema Bancário pode ocasionar grandes transtornos para o Banco, assim como em qualquer outra ocasião onde se faz necessária a manipulação de dinheiro. Sabendo disto, explicaremos neste artigo porque utilizar a classe BigDecimal em vez do Double para realizar operações monetárias, assim como as funções mais comuns e utilizadas do BigDecimal e como utilizá-las.

Dica: Não deixe de conferir os Cursos de Java da DevMedia.

Double vs BigDecimal

Ao trabalhar com dinheiro precisamos de valores exatos, nem 1 centavo a menos nem 1 centavo a mais, afinal, há muita coisa envolvida neste tipo de processo. Quando trabalhamos com Double (seja o Double wrapper ou mesmo o double tipo primitivo) não há uma precisão de casas decimais que precisamos, podemos perceber isso em somas simples onde o resultado deveria ser simples, mas acaba sendo estranho e confuso. Veja o exemplo na Listagem 1.


  public class MyAppBigDecimal {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               double d1 = 0.1;
               double d2 = 0.2;
               System.out.println(d1 + d2);
   
         }
   
  }
Listagem 1. Double gerando valores estranhos

A saída do código acima será “0.30000000000000004”, mas nós esperávamos apenas um “0.3”, não? O problema todo está na forma de representar o tipo double na JVM, pois ela adota uma representação binária que segue o padrão IEEE 754.

Pelo fato da JVM trabalhar com representação binária para o tipo double, o valor “0.1” é transformado para binário e vira uma dízima periódica, ou seja, um valor infinito, algo como “0.110011001100..”.

Agora vamos realizar o exemplo da Listagem 1, mas com o d1 = 0.25, que é representado por “0.1” em binário, ou seja, é um valor finito e exato. Observe a Listagem 2.


   public class MyAppBigDecimal {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               double d1 = 0.25;
               double d2 = 0.2;
               System.out.println(d1 + d2);
   
         }
   
  }
Listagem 2. Double gerando valor correto com 0.25

A saída do código acima será exatamente o que você espera: “0.45”, isso porque como dissemos anteriormente, o 0.25 é um número representado de forma finita pela JVM.

Se você ainda não está convencido que o tipo double pode lhe causar problemas, sérios problemas quando você precisa de exatidão nos valores, vamos ver na Listagem 3 vários valores sendo calculados de forma imprecisa pelo double.

 
 public class MyAppBigDecimal {
 
   /**
    * @param args
    */
   public static void main(String[] args) {
     System.out.println(2.00 - 1.1);
     System.out.println(2.00 - 1.2);
     System.out.println(2.00 - 1.3);
     System.out.println(2.00 - 1.4);
     System.out.println(2.00 - 1.5);
     System.out.println(2.00 - 1.6);
     System.out.println(2.00 - 1.7);
     System.out.println(2.00 - 1.8);
     System.out.println(2.00 - 1.9);
     System.out.println(2.00 - 2);

   }
 
}
 
Saída:
0.8999999999999999
0.8
0.7
0.6000000000000001
0.5
0.3999999999999999
0.30000000000000004
0.19999999999999996
0.10000000000000009
0.0
Listagem 3. Calculando valores com double

Dada as situações apresentadas acima, é perceptível que não podemos contar com o double para realizar cálculos que envolvemos precisão. E não só para dinheiro, mas para qualquer outra regra de negócio que exija precisão em todas as casas decimais como por exemplo, projetos de construção civil. Mas então qual a saída para toda essa precisão? Utilizar a classe BigDecimal.

A classe BigDecimal trabalha com pontos flutuantes de precisão arbitrária, ou seja, você consegue estipular qual o nível de precisão você precisa trabalhar. Diferentemente do double que trabalha com representação binária, o BigDecimal guardará seu valor usando a base decimal. Por exemplo: enquanto que o double tenta representar o valor “0.1” em binário e encontra uma dízima, o BigDecimal representa o mesmo valor através da base decimal “1 x 10^-1”.

Utilizando o mesmo exemplo da Listagem 1, somaremos o valor “0.1” + “0.2”, mas agora com BigDecimal. Veremos que a saída será outra, conforme a Listagem 4.

 
import java.math.BigDecimal;
  
public class MyAppBigDecimal {
 
   /**
    * @param args
    */
   public static void main(String[] args) {
         BigDecimal big1 = new BigDecimal("0.1");
         BigDecimal big2 = new BigDecimal("0.2");
         BigDecimal bigResult = big1.add(big2);
         System.out.println(bigResult.toString());

   }
 
}
Listagem 4. Somando valores com BigDecimal

A saída do código acima será exatamente “0.3”. Mas cuidado, o construtor do BigDecimal pode trazer surpresas inesperadas e acabar causando grandes dores de cabeça.

Perceba que no código da Listagem 4 usamos o construtor do BigDecimal passando como parâmetro Strings e não valores “doubles”, isso porque o próprio javadoc deste construtor nos aconselha a usar construtor do BigDecimal com Strings. Veja o que ocorre no exemplo da Listagem 5.


  import java.math.BigDecimal;
    
  public class MyAppBigDecimal {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               BigDecimal big1 = new BigDecimal(0.1);
               BigDecimal big2 = new BigDecimal(0.2);
               BigDecimal bigResult = big1.add(big2);
               System.out.println(bigResult.toString());
   
         }
   
  }
   Saída:
  0.3000000000000000166533453693773481063544750213623046875
Listagem 5. Usando construtor double do BigDecimal

Acontece que os números 0.1, e 0.2 já são do tipo double e ao passar para o construtor do BigDecimal ocorre uma transformação com imprecisão, que é exatamente o que não queremos. Então fica o conselho: utilize sempre Strings no construtor do BigDecimal.

Sempre que trabalhar com BigDecimal você precisa chamar seus métodos para realizar operações aritméticas, assim teremos garantia da precisão que tanto precisamos. Sendo assim, você jamais poderá fazer “bigDecimal1 + bigDecimal2” ou mesmo “bigDecimal1 – bigDecimal2”. Para isso existem métodos específicos, que veremos na Listagem 6.

 
import java.math.BigDecimal;
 public class MyAppBigDecimal {
 
     /**
      * @param args
      */
     public static void main(String[] args) {
         System.out.println("Subtrai");
         System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.1")));

         System.out.println("");
         System.out.println("Soma");
         System.out.println(new BigDecimal("2.00").add(new BigDecimal("1.2")));

         System.out.println("");
         System.out.println("Compara");
         System.out.println(new BigDecimal("2.00").compareTo(new BigDecimal("1.3")));

         System.out.println("");
         System.out.println("Divide");
         System.out.println(new BigDecimal("2.00").divide(new BigDecimal("2.00")));

         System.out.println("");
         System.out.println("Máximo");
         System.out.println(new BigDecimal("2.00").max(new BigDecimal("1.5")));

         System.out.println("");
         System.out.println("Mínimo");
         System.out.println(new BigDecimal("2.00").min(new BigDecimal("1.6")));

         System.out.println("");
         System.out.println("Potência");
         System.out.println(new BigDecimal("2.00").pow(2));

         System.out.println("");
         System.out.println("Multiplica");
         System.out.println(new BigDecimal("2.00").multiply(new BigDecimal("1.8")));

     }
 
}
Listagem 6. Realizando operações aritméticas com BigDecimal

Temos ainda um caso interessante no BigDecimal: este não faz nenhum tipo de arredondamento por si próprio, ou seja, você precisa especificar como deseja arredondar determinado valor se for necessário, caso contrário, ele lançará uma exceção java.lang.ArithmeticException. Quando desejamos dividir 1/3, por exemplo, o resultado seria uma dízima periódica, e como o BigDecimal não arredondará o valor para você, ele retornará uma exceção apontando este problema. Sendo assim, precisamos dizer explicitamente como desejamos que o arredondamento ocorra caso haja uma dízima. Vejamos a Listagem 7.

 
  //Código errado retornando o Exception
  import java.math.BigDecimal;
   
  public class MyAppBigDecimal {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               
               System.out.println("Divide");
               System.out.println(new BigDecimal("1.00")
               .divide(new BigDecimal("1.3")));
   
   
         }
   
  }
  Saída:
  Divide
  Exception in thread "main" java.lang.ArithmeticException: 
  Non-terminating decimal expansion; no exact representable decimal result.
         at java.math.BigDecimal.divide(BigDecimal.java:1603)
         at MyAppBigDecimal.main(MyAppBigDecimal.java:11)
   
   
   
  //Código Correto retornando valor arredondado
  import java.math.BigDecimal;
  import java.math.RoundingMode;
   
  public class MyAppBigDecimal {
   
         /**
          * @param args
          */
         public static void main(String[] args) {
               
               System.out.println("Divide");
               System.out.println(new BigDecimal("1.00").divide
               (new BigDecimal("1.3"),3,RoundingMode.UP));
   
   
         }
   
  }
  Saída:
  Divide
  0.770
Listagem 7. Arredondando para evitar ArithmeticException

Perceba que no código acima optamos por arredondar o valor para cima e deixar três casas decimais após a virgula.

Com este artigo foi mostrado os erros do double quando precisamos trabalhar com valores precisos e que não admitem erros, como solução a este problema, apresentamos a classe BigDecimal, apontando seus princípios, fundamentos e problemas. Mostramos também, de forma científica, porque o double apresenta erros de conversão que na verdade nada mais são do que representações binárias errôneas. Esperamos que com este artigo, você leitor possa fazer bom uso da classe BigDecimal e não mais ter dores de cabeças com valores errados em relatórios no final do mês.