Java BigDecimal e Dinheiro
Para o computador, 0.1 + 0.2 não é 0.3. Num jogo, ninguém percebe a diferença; numa loja, num banco ou numa folha de pagamento, um centavo errado é um problema sério. O Java resolve isso com a classe BigDecimal, que faz contas com números decimais exatos, e a sua irmã BigInteger, para inteiros de qualquer tamanho.
- Por que o
doubleerra centavos, com exemplos reais. - Criar um
BigDecimaldo jeito certo (new BigDecimal("0.1")) e o jeito errado (new BigDecimal(0.1)). - Fazer contas com
add,subtract,multiplyedivide. - Arredondar com
setScaleeRoundingMode. - A
ArithmeticExceptionda divisão que não termina, e como evitá-la. - Comparar valores com
compareTo(e por queequalsengana). - Calcular o fatorial de 50 com
BigInteger.
O problema: double não sabe 0,1
Você já viu em double e float que o double guarda números em binário (base 2). Assim como 1/3 não tem representação exata em decimal (0,3333…), o número 0,1 não tem representação exata em binário: o double guarda o valor mais próximo possível, que é um pouquinho diferente. Na maioria das contas, o erro some ao imprimir. Nas contas com dinheiro, ele aparece:
public class Main {
public static void main(String[] args) {
System.out.println(0.1 + 0.2);
System.out.println(0.1 + 0.2 == 0.3);
double total = 0;
for (int i = 0; i < 10; i++) {
total += 0.10;
}
System.out.println("10 moedas de 10 centavos: " + total);
System.out.println(1.10 - 1.00);
System.out.println(4.35 * 100);
System.out.println((int) (4.35 * 100));
}
}
$ java Main.java 0.30000000000000004 false 10 moedas de 10 centavos: 0.9999999999999999 0.10000000000000009 434.99999999999994 434
0.1 + 0.2dá0.30000000000000004, e por isso a comparação com0.3éfalse.- Dez moedas de R$ 0,10 não somam R$ 1,00. Some milhares de vendas assim e a diferença cresce.
- R$ 4,35 em centavos, convertendo para
int, vira 434: o(int)corta as casas decimais (veja Conversão de Tipos), e o valor era 434,99999…
Não é defeito do Java: toda linguagem que usa o padrão de ponto flutuante IEEE 754 (JavaScript, Python, C…) tem o mesmo comportamento. A regra dos programadores é antiga e simples: dinheiro não se guarda em double.
BigDecimal: decimais exatos
O BigDecimal ("decimal grande", do pacote java.math) guarda o número como nós escrevemos: os dígitos e a posição da vírgula. 0.1 é exatamente um décimo.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
System.out.println(new BigDecimal("0.1"));
System.out.println(new BigDecimal(0.1));
System.out.println(BigDecimal.valueOf(0.1));
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 10; i++) {
total = total.add(new BigDecimal("0.10"));
}
System.out.println("10 moedas de 10 centavos: " + total);
}
}
$ java Main.java 0.3 0.1 0.1000000000000000055511151231257827021181583404541015625 0.1 10 moedas de 10 centavos: 1.00
- import java.math.BigDecimal;
- A classe fica em
java.math, que não é importado automaticamente. - new BigDecimal("0.1")
- Criado a partir de um texto: exatamente 0,1. É o jeito certo. O texto usa ponto, como o Java.
- a.add(b)
- Soma. Não existe
+paraBigDecimal: as contas são métodos. Resultado:0.3, exato. - new BigDecimal(0.1)
- Criado a partir de um
double. Odouble0.1já chega errado, e oBigDecimalmostra fielmente o valor que odoublerealmente tinha:0.1000000000000000055511151231257827021181583404541015625. É o erro escondido dodouble, revelado. - BigDecimal.valueOf(0.1)
- Se o valor já está num
double, esta é a forma segura: ela passa pelo texto que odoublemostraria ("0.1") e dá0.1. - BigDecimal.ZERO
- Constante pronta para o zero (também há
ONEeTEN). - total = total.add(…)
- Como a
String, oBigDecimalé imutável:adddevolve umBigDecimalnovo. É preciso guardar o resultado.
new BigDecimal(0.1)
Criar a partir de um double traz junto o erro do double. Use new BigDecimal("0.1") (texto) ou BigDecimal.valueOf(0.1). Números inteiros não têm esse problema: new BigDecimal(3) e BigDecimal.valueOf(3) são exatos.
Missão: a soma das dez moedas dá 0.9999999999999999. Reescreva com BigDecimal: comece em BigDecimal.ZERO e, no laço, faça total = total.add(new BigDecimal("0.10")). A saída deve ser Total: 1.00.
public class Main {
public static void main(String[] args) {
double total = 0;
for (int i = 0; i < 10; i++) {
total += 0.10;
}
System.out.println("Total: " + total);
}
}
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 10; i++) {
total = total.add(new BigDecimal("0.10"));
}
System.out.println("Total: " + total);
}
}
As quatro operações e a escala
A Ana vende 3 cadernos de R$ 19,90, dá 15% de desconto e parcela em 4 vezes:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal preco = new BigDecimal("19.90");
BigDecimal quantidade = new BigDecimal("3");
BigDecimal subtotal = preco.multiply(quantidade);
System.out.println("Subtotal: " + subtotal);
BigDecimal desconto = subtotal.multiply(new BigDecimal("0.15"));
System.out.println("Desconto de 15%: " + desconto);
BigDecimal descontoArredondado = desconto.setScale(2, RoundingMode.HALF_UP);
System.out.println("Desconto arredondado: " + descontoArredondado);
BigDecimal total = subtotal.subtract(descontoArredondado);
System.out.println("Total: " + total);
BigDecimal parcela = total.divide(new BigDecimal("4"), 2, RoundingMode.HALF_UP);
System.out.println("4 parcelas de: " + parcela);
System.out.println("Escala do total: " + total.scale());
}
}
$ java Main.java Subtotal: 59.70 Desconto de 15%: 8.9550 Desconto arredondado: 8.96 Total: 50.74 4 parcelas de: 12.69 Escala do total: 2
| Operação | Método | No exemplo |
|---|---|---|
| Soma | a.add(b) | |
| Subtração | a.subtract(b) | 59.70 - 8.96 = 50.74 |
| Multiplicação | a.multiply(b) | 19.90 × 3 = 59.70 |
| Divisão | a.divide(b, casas, arredondamento) | 50.74 ÷ 4 = 12.685 → 12.69 |
| Negar, valor absoluto | a.negate(), a.abs() | |
| Potência | a.pow(n) |
A escala: quantas casas depois do ponto
Todo BigDecimal tem uma escala (scale): o número de casas decimais que ele guarda. 19.90 tem escala 2 (e o zero do fim é mantido: 59.70, não 59.7). Na multiplicação, as escalas se somam: 59.70 (2 casas) × 0.15 (2 casas) = 8.9550 (4 casas). Por isso, depois de multiplicar por uma porcentagem, arredonde para centavos.
- setScale(2, RoundingMode.HALF_UP)
- "Deixe com 2 casas, arredondando meio para cima":
8.9550vira8.96. Também devolve umBigDecimalnovo. - divide(new BigDecimal("4"), 2, RoundingMode.HALF_UP)
- Divisão já com o número de casas e a regra de arredondamento:
12.685vira12.69. - scale()
- Devolve a escala:
2.
Os modos de arredondamento (RoundingMode)
Arredondar parece simples até alguém perguntar: "e o 5?". O RoundingMode (pacote java.math) tem as regras todas. Cinco valores arredondados para 2 casas, de cinco jeitos:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
String[] valores = {"2.345", "2.355", "-2.345", "2.341", "2.349"};
System.out.printf("%-8s %-8s %-9s %-8s %-6s %s%n", "valor", "HALF_UP", "HALF_EVEN", "DOWN", "UP", "FLOOR");
for (String v : valores) {
BigDecimal b = new BigDecimal(v);
System.out.printf("%-8s %-8s %-9s %-8s %-6s %s%n", v,
b.setScale(2, RoundingMode.HALF_UP),
b.setScale(2, RoundingMode.HALF_EVEN),
b.setScale(2, RoundingMode.DOWN),
b.setScale(2, RoundingMode.UP),
b.setScale(2, RoundingMode.FLOOR));
}
}
}
$ java Main.java valor HALF_UP HALF_EVEN DOWN UP FLOOR 2.345 2.35 2.34 2.34 2.35 2.34 2.355 2.36 2.36 2.35 2.36 2.35 -2.345 -2.35 -2.34 -2.34 -2.35 -2.35 2.341 2.34 2.34 2.34 2.35 2.34 2.349 2.35 2.35 2.34 2.35 2.34
| Modo | Regra | Quando usar |
|---|---|---|
HALF_UP | O 5 sobe (o da escola): 2,345 → 2,35 | O mais comum em preços e notas fiscais |
HALF_EVEN | O 5 vai para o vizinho par: 2,345 → 2,34; 2,355 → 2,36 | "Arredondamento bancário": em muitas contas, os arredondamentos para cima e para baixo se compensam |
DOWN | Corta as casas (em direção ao zero) | Truncar: "não cobrar fração de centavo" |
UP | Sempre se afasta do zero se sobrou algo: 2,341 → 2,35 | Cobrar a fração (taxas mínimas) |
FLOOR / CEILING | Sempre para baixo / para cima na reta (diferem do DOWN/UP nos negativos) | Regras específicas |
Qual usar com dinheiro é uma decisão do negócio (ou da lei), não do programador. Na dúvida, HALF_UP é o que as pessoas esperam.
A divisão que não termina: ArithmeticException
Dividir R$ 100,00 por 4 dá exatamente 25,00. Mas por 3 dá 33,3333… para sempre. Como o BigDecimal promete ser exato, e essa dízima não tem fim, ele se recusa:
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal conta = new BigDecimal("100.00");
BigDecimal pessoas = new BigDecimal("3");
System.out.println(conta.divide(new BigDecimal("4")));
System.out.println(conta.divide(pessoas));
}
}
$ java Main.java 25.00 Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.base/java.math.BigDecimal.divide(BigDecimal.java:1810) at Main.main(Main.java:8)
"Expansão decimal sem fim; não há resultado decimal exato representável". O divide com um argumento só funciona quando a divisão termina. A solução é sempre dizer quantas casas e como arredondar: conta.divide(pessoas, 2, RoundingMode.HALF_UP) dá 33.33. E aí aparece outra pergunta do mundo real: 3 × 33,33 = 99,99. Quem paga o centavo que sobrou? A missão do fim da aula calcula isso.
Comparar: compareTo, não equals
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("2.00");
System.out.println("equals: " + a.equals(b));
System.out.println("compareTo: " + a.compareTo(b));
System.out.println("iguais? " + (a.compareTo(b) == 0));
BigDecimal saldo = new BigDecimal("50.00");
BigDecimal saque = new BigDecimal("80");
if (saque.compareTo(saldo) > 0) {
System.out.println("Saque maior que o saldo");
}
System.out.println("Maior: " + saldo.max(saque));
System.out.println("Sem zeros: " + b.stripTrailingZeros());
}
}
$ java Main.java equals: false compareTo: 0 iguais? true Saque maior que o saldo Maior: 80 Sem zeros: 2
2.0 e 2.00 têm o mesmo valor, mas escalas diferentes (1 e 2 casas). O equals do BigDecimal compara valor e escala, por isso dá false. O compareTo compara só o valor, e devolve um número:
a.compareTo(b) | Quer dizer | Equivale a (com números comuns) |
|---|---|---|
| negativo | a é menor | a < b → a.compareTo(b) < 0 |
0 | valores iguais | a == b → a.compareTo(b) == 0 |
| positivo | a é maior | a > b → a.compareTo(b) > 0 |
Truque para ler: escreva a comparação que você queria (saque > saldo), troque os dois lados por saque.compareTo(saldo) e compare com zero usando o mesmo sinal (> 0). O compareTo é o mesmo método que o Java usa para ordenar (veja Comparable e Comparator). E nada de <, > ou == direto entre BigDecimal: são objetos.
Mostrando dinheiro
O toString do BigDecimal usa ponto. Para mostrar ao usuário no formato brasileiro, use o NumberFormat da aula Formatar Texto, que aceita BigDecimal diretamente:
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
BigDecimal total = new BigDecimal("1234.5");
NumberFormat real = NumberFormat.getCurrencyInstance(Locale.of("pt", "BR"));
System.out.println(real.format(total));
System.out.println(total.doubleValue());
System.out.println(total.intValue());
}
}
$ java Main.java R$ 1.234,50 1234.5 1234
doubleValue() e intValue() convertem de volta para primitivos (o intValue corta as casas). Use-os só na saída, nunca para continuar fazendo contas com dinheiro. O printf("%.2f", total) também aceita BigDecimal.
BigInteger: inteiros sem limite
O long vai até 9.223.372.036.854.775.807 (veja int, long, short, byte). Passou disso, ele "dá a volta" e vira negativo, sem aviso. O fatorial cresce muito rápido (5! = 5 × 4 × 3 × 2 × 1 = 120) e mostra bem o problema. O BigInteger, também de java.math, cresce o quanto for preciso:
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
long fatorialLong = 1;
BigInteger fatorial = BigInteger.ONE;
for (int i = 1; i <= 50; i++) {
fatorialLong *= i;
fatorial = fatorial.multiply(BigInteger.valueOf(i));
if (i == 20 || i == 21 || i == 50) {
System.out.println(i + "! com long: " + fatorialLong);
System.out.println(i + "! com BigInteger: " + fatorial);
}
}
System.out.println("Dígitos de 50!: " + fatorial.toString().length());
System.out.println("2 elevado a 100: " + BigInteger.TWO.pow(100));
}
}
$ java Main.java 20! com long: 2432902008176640000 20! com BigInteger: 2432902008176640000 21! com long: -4249290049419214848 21! com BigInteger: 51090942171709440000 50! com long: -3258495067890909184 50! com BigInteger: 30414093201713378043612608166064768844377641568960512000000000000 Dígitos de 50!: 65 2 elevado a 100: 1267650600228229401496703205376
Até 20!, os dois concordam. Em 21!, o long estoura (overflow) e mostra um número negativo absurdo; o BigInteger continua certo até 50!, um número de 65 dígitos. Os métodos são os mesmos do BigDecimal (add, subtract, multiply, divide, pow, compareTo), com BigInteger.valueOf(n) para criar a partir de um long e constantes ZERO, ONE, TWO, TEN. Ele é usado em criptografia, em cálculos científicos e em qualquer conta que possa passar do limite do long.
Qual tipo usar
| Situação | Tipo |
|---|---|
| Dinheiro, preços, impostos, juros | BigDecimal (criado com texto) |
| Medidas físicas, gráficos, estatística, jogos | double: rápido, e o erro minúsculo não importa |
| Contagens, quantidades inteiras | int ou long |
| Inteiros maiores que 9,2 × 1018 | BigInteger |
| Alternativa simples para dinheiro | long com o valor em centavos (1990 = R$ 19,90). Funciona, mas porcentagens e divisões exigem cuidado |
Quando dá errado
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal preco = new BigDecimal("10.50");
BigDecimal total = preco * 2;
BigDecimal frete = 5.00;
}
}
$ java Main.java
Main.java:6: error: bad operand types for binary operator '*'
BigDecimal total = preco * 2;
^
first type: BigDecimal
second type: int
Main.java:7: error: incompatible types: double cannot be converted to BigDecimal
BigDecimal frete = 5.00;
^
2 errors
error: compilation failed
O que aconteceu: os operadores + - * / só funcionam com primitivos (e o + com String). E não existe "autoboxing" de double para BigDecimal.
Como resolver: preco.multiply(new BigDecimal("2")) (ou BigDecimal.valueOf(2)) e new BigDecimal("5.00").
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal saldo = new BigDecimal("100.00");
saldo.add(new BigDecimal("50.00"));
System.out.println("Saldo depois do depósito: " + saldo);
}
}
$ java Main.java Saldo depois do depósito: 100.00
O que aconteceu: BigDecimal é imutável; o add devolveu 150.00 e o resultado foi descartado.
Como resolver: saldo = saldo.add(new BigDecimal("50.00"));.
O que aconteceu: um divide(x) sem escala, numa divisão que dá dízima (por 3, 6, 7…).
Como resolver: sempre que dividir dinheiro, informe as casas e o arredondamento: divide(x, 2, RoundingMode.HALF_UP).
equals falso
O que aconteceu: escalas diferentes (2.0 × 2.00, ou 10 × 10.00).
Como resolver: compare com a.compareTo(b) == 0.
Referência
| Código | O que faz |
|---|---|
new BigDecimal("19.90") | Cria a partir de texto (o jeito certo) |
BigDecimal.valueOf(d) | Cria a partir de um double ou long, com segurança |
BigDecimal.ZERO, ONE, TEN | Constantes |
add, subtract, multiply | Soma, subtração, multiplicação (devolvem um novo) |
divide(b, 2, RoundingMode.HALF_UP) | Divisão com 2 casas |
setScale(2, RoundingMode.HALF_UP) | Arredonda para 2 casas |
scale() | Quantas casas decimais |
a.compareTo(b) | Negativo, 0 ou positivo |
max, min, abs, negate | Maior, menor, absoluto, negativo |
stripTrailingZeros() | Tira zeros do fim |
doubleValue(), intValue() | Converte para primitivo (só na saída) |
BigInteger.valueOf(n), multiply, pow | Inteiros sem limite |
Exercícios
1. Criar do jeito certo
Complete para criar exatamente 0,1:
2. Somar
Complete para somar b ao total, guardando o resultado:
3. Arredondar
Complete para deixar valor com 2 casas, arredondando o 5 para cima:
4. O que System.out.println(0.1 + 0.2); mostra?
5. new BigDecimal("5.0").equals(new BigDecimal("5.00")) dá…
6. Que linha lança ArithmeticException?
Missão no JShell: calcule 0.1 + 0.2 com BigDecimal, criando os dois números a partir de texto, e confira que o resultado é exatamente 0.3. (O JShell já importa java.math.) Depois, experimente new BigDecimal(0.1).
new BigDecimal("0.1").add(new BigDecimal("0.2"))
Missão final: a conta de R$ 100,00 será dividida entre 3 amigos. O programa quebra com ArithmeticException. Corrija a divisão para 2 casas com RoundingMode.HALF_UP e calcule a sobra: conta menos cada × pessoas. Saída esperada: Cada um paga: 33.33 e Sobra: 0.01.
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal conta = new BigDecimal("100.00");
BigDecimal pessoas = new BigDecimal("3");
BigDecimal cada = conta.divide(pessoas);
System.out.println("Cada um paga: " + cada);
}
}
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal conta = new BigDecimal("100.00");
BigDecimal pessoas = new BigDecimal("3");
BigDecimal cada = conta.divide(pessoas, 2, RoundingMode.HALF_UP);
BigDecimal sobra = conta.subtract(cada.multiply(pessoas));
System.out.println("Cada um paga: " + cada);
System.out.println("Sobra: " + sobra);
}
}
Resumo
doubleguarda decimais em binário e erra frações como 0,1:0.1 + 0.2dá0.30000000000000004. Não use para dinheiro.BigDecimalé exato. Crie com texto (new BigDecimal("0.1")) ouBigDecimal.valueOf; nunca comnew BigDecimal(0.1).- Contas são métodos (
add,subtract,multiply,divide) e devolvem um objeto novo: guarde o resultado. setScale(2, RoundingMode.HALF_UP)arredonda para centavos; escolha oRoundingModede propósito.dividesem escala lançaArithmeticExceptionquando o resultado não termina: usedivide(x, 2, RoundingMode.HALF_UP).- Compare com
compareTo(oequalsconsidera a escala:2.0 ≠ 2.00). BigIntegerfaz contas com inteiros de qualquer tamanho: 50! tem 65 dígitos.