Double vs. BigDecimal?

303

Eu tenho que calcular algumas variáveis ​​de ponto flutuante e meu colega sugere que eu use em BigDecimalvez de, doublepois será mais preciso. Mas quero saber o que é e como tirar o máximo proveito BigDecimal?

Truong Ha
fonte
Confira este; stackoverflow.com/questions/322749/…
Espen Schulstad

Respostas:

446

A BigDecimalé uma maneira exata de representar números. A Doubletem uma certa precisão. Trabalhar com dobras de várias magnitudes (digamos d1=1000.0e d2=0.001) pode resultar em uma 0.001queda total ao somar uma vez que a diferença de magnitude é muito grande. Com BigDecimalisso não aconteceria.

A desvantagem BigDecimalé que é mais lento e é um pouco mais difícil programar algoritmos dessa maneira (devido + - *e /sem sobrecarregar).

Se você está lidando com dinheiro, ou a precisão é uma obrigação, use BigDecimal. Caso contrário, Doublestendem a ser bons o suficiente.

Eu recomendo a leitura do javadoc de BigDecimalcomo eles fazem explicar as coisas melhor do que eu aqui :)

extraneon
fonte
Sim, estou calculando o preço das ações, por isso acredito que o BigDecimal é útil nesse caso.
Truong Ha
5
@ Truong Ha: Ao trabalhar com preços, você deseja usar o BigDecimal. E se você armazená-los no banco de dados, deseja algo semelhante.
extraneon
98
Dizer que "BigDecimal é uma maneira exata de representar números" é enganador. 1/3 e 1/7 não podem ser expressos exatamente em um sistema numérico de base 10 (BigDecimal) ou no sistema de números base 2 (flutuante ou duplo). 1/3 pode ser expresso exatamente na base 3, base 6, base 9, base 12, etc. e 1/7 pode ser expresso exatamente na base 7, base 14, base 21, etc. As vantagens do BigDecimal são a precisão arbitrária e que os seres humanos são usados para os erros de arredondamento que você começa na base 10.
procrastinate_later
3
Bom ponto sobre ele ser mais lento, me ajuda a entender por que a fita Netflix ofertas de código de balanceamento de carga com duplas, e depois tem linhas como esta:if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
michaelok
@extraneon Acho que você quer dizer "se a precisão é obrigatória, use BigDecimal", um Double teria mais "precisão" (mais dígitos).
jspinella
164

Meu inglês não é bom, então vou escrever um exemplo simples aqui.

    double a = 0.02;
    double b = 0.03;
    double c = b - a;
    System.out.println(c);

    BigDecimal _a = new BigDecimal("0.02");
    BigDecimal _b = new BigDecimal("0.03");
    BigDecimal _c = _b.subtract(_a);
    System.out.println(_c);

Saída do programa:

0.009999999999999998
0.01

Alguém ainda quer usar o dobro? ;)

Manjericão
fonte
11
@eldjon Isso não é verdade, veja este exemplo: BigDecimal two = new BigDecimal ("2"); BigDecimal oito = novo BigDecimal ("8"); System.out.println (two.divide (oito)); Isso imprime 0,25.
Ludvig W
4
dobra forevr: D
vach
No entanto, se você usar um float em vez disso você obter a mesma precisão do que BigDecimal, nesse caso, mas melhor desempenho maneira
EliuX
3
@EliuX Float pode trabalhar com 0,03-0,02, mas outros valores ainda são imprecisos: System.out.println(0.003f - 0.002f);BigDecimal é exata:System.out.println(new BigDecimal("0.003").subtract(new BigDecimal("0.002")));
Martin
50

Existem duas diferenças principais em relação ao dobro:

  • Precisão arbitrária, semelhante ao BigInteger, eles podem conter número de precisão e tamanho arbitrários
  • Base 10 em vez de Base 2, um BigDecimal é n * 10 ^ escala em que n é um número inteiro grande e arbitrário e a escala pode ser considerada o número de dígitos para mover o ponto decimal para a esquerda ou para a direita

O motivo pelo qual você deve usar o BigDecimal para cálculos monetários não é que ele possa representar qualquer número, mas que ele possa representar todos os números que podem ser representados na noção decimal e que incluem praticamente todos os números do mundo monetário (você nunca transfere 1/3 $ para alguém).

Meros
fonte
2
Esta resposta realmente explica a diferença e o motivo de usar o BigDecimal mais do que o dobro. As preocupações com o desempenho são secundárias.
Vortex
Isso não é 100% verdade. Você escreveu que um BigDecimal é "n * 10 ^ scale". Java faz isso apenas para números negativos. Tão correto seria: "unscaledValue × 10 ^ -scale". Para números positivos, o BigDecimal consiste em um "valor inteiro não escalonado de precisão arbitrária e uma escala inteira de 32 bits", enquanto a escala é o número de dígitos à direita do ponto decimal.
a mão de NOD
25

Se você escrever um valor fracionário como 1 / 7valor decimal, obtém

1/7 = 0.142857142857142857142857142857142857142857...

com uma sequência infinita de 142857. Como você só pode escrever um número finito de dígitos, inevitavelmente introduzirá um erro de arredondamento (ou truncamento).

Números como 1/10ou 1/100expressos como números binários com uma parte fracionária também têm um número infinito de dígitos após o ponto decimal:

1/10 = binary 0.0001100110011001100110011001100110...

Doubles armazene valores como binários e, portanto, poderá introduzir um erro apenas convertendo um número decimal em um número binário, sem fazer aritmética.

Os números decimais (como BigDecimal), por outro lado, armazenam cada dígito decimal como está. Isso significa que um tipo decimal não é mais preciso que um ponto flutuante binário ou tipo de ponto fixo em um sentido geral (ou seja, não pode ser armazenado 1/7sem perda de precisão), mas é mais preciso para números que tenham um número finito de dígitos decimais como é frequentemente o caso dos cálculos de dinheiro.

O Java BigDecimaltem a vantagem adicional de poder ter um número arbitrário (mas finito) de dígitos em ambos os lados do ponto decimal, limitado apenas pela memória disponível.

Olivier Jacot-Descombes
fonte
7

BigDecimal é a biblioteca numérica de precisão arbitrária da Oracle. O BigDecimal faz parte da linguagem Java e é útil para uma variedade de aplicativos que variam do financeiro ao científico (é aí que estou).

Não há nada de errado em usar o dobro para determinados cálculos. Suponha, no entanto, que você queira calcular Math.Pi * Math.Pi / 6, ou seja, o valor da função Riemann Zeta para um argumento real de dois (um projeto no qual estou trabalhando atualmente). A divisão de ponto flutuante apresenta um problema doloroso de erro de arredondamento.

O BigDecimal, por outro lado, inclui muitas opções para calcular expressões com precisão arbitrária. Os métodos de adição, multiplicação e divisão, conforme descrito na documentação do Oracle abaixo "substituem", +, * e / no BigDecimal Java World:

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

O método compareTo é especialmente útil em loops while e for.

Tenha cuidado, no entanto, ao usar construtores para BigDecimal. O construtor de strings é muito útil em muitos casos. Por exemplo, o código

BigDecimal onethird = new BigDecimal ("0.33333333333");

utiliza uma representação em cadeia de 1/3 para representar esse número de repetição infinita com um grau especificado de precisão. O erro de arredondamento provavelmente está em algum lugar tão profundo na JVM que os erros de arredondamento não perturbarão a maioria dos seus cálculos práticos. Eu, por experiência própria, vi o arredondamento surgir, no entanto. O método setScale é importante nesses aspectos, como pode ser visto na documentação do Oracle.

fishermanhat
fonte
BigDecimal faz parte da biblioteca numérica de precisão arbitrária do Java . 'In-house' não tem sentido nesse contexto, especialmente como foi escrito pela IBM.
Marquês de Lorne
@EJP: Examinei a classe BigDecimal e aprendi que apenas uma parte dela é escrita pela IBM. Comentário de direitos autorais abaixo: /* * Portions Copyright IBM Corporation, 2001. All Rights Reserved. */
realPK
7

Se você está lidando com cálculos, existem leis sobre como você deve calcular e qual precisão você deve usar. Se você falhar, estará fazendo algo ilegal. A única razão real é que a representação de bits das casas decimais não é precisa. Como Basil simplesmente coloca, um exemplo é a melhor explicação. Apenas para complementar seu exemplo, eis o que acontece:

static void theDoubleProblem1() {
    double d1 = 0.3;
    double d2 = 0.2;
    System.out.println("Double:\t 0,3 - 0,2 = " + (d1 - d2));

    float f1 = 0.3f;
    float f2 = 0.2f;
    System.out.println("Float:\t 0,3 - 0,2 = " + (f1 - f2));

    BigDecimal bd1 = new BigDecimal("0.3");
    BigDecimal bd2 = new BigDecimal("0.2");
    System.out.println("BigDec:\t 0,3 - 0,2 = " + (bd1.subtract(bd2)));
}

Resultado:

Double:  0,3 - 0,2 = 0.09999999999999998
Float:   0,3 - 0,2 = 0.10000001
BigDec:  0,3 - 0,2 = 0.1

Também temos que:

static void theDoubleProblem2() {
    double d1 = 10;
    double d2 = 3;
    System.out.println("Double:\t 10 / 3 = " + (d1 / d2));

    float f1 = 10f;
    float f2 = 3f;
    System.out.println("Float:\t 10 / 3 = " + (f1 / f2));

    // Exception! 
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4)));
}

Nos dá a saída:

Double:  10 / 3 = 3.3333333333333335
Float:   10 / 3 = 3.3333333
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion

Mas:

static void theDoubleProblem2() {
    BigDecimal bd3 = new BigDecimal("10");
    BigDecimal bd4 = new BigDecimal("3");
    System.out.println("BigDec:\t 10 / 3 = " + (bd3.divide(bd4, 4, BigDecimal.ROUND_HALF_UP)));
}

Possui a saída:

BigDec:  10 / 3 = 3.3333 
jfajunior
fonte