Podemos usar o dobro para armazenar campos monetários e usar BigDecimal para aritmética

Eu sei o problema com double / float, e é recomendado usar BigDecimal em vez de double / float para representar campos monetários. Mas o double / float é mais efetivo e economiza espaço. Então minha pergunta é: É aceitável usar double / float para representar campos monetários na class Java, mas use BigDecimal para cuidar da aritmética (isto é, converter double / float para BigDecimal antes de qualquer aritmética) e checar com igualdade?

O motivo é economizar algum espaço. E eu realmente vejo muitos projetos usando double / float para representar os campos monetários.

Existe alguma armadilha para isso? Desde já, obrigado.

Não, você não pode.

Suponha que o double seja suficiente para armazenar dois valores x e y . Então você os converte em BigDecimal seguro e os multiplica. O resultado é preciso, no entanto, se você armazenar o resultado da multiplicação novamente em double , provavelmente perderá a precisão. Prova:

 double x = 1234567891234.0; double y = 1234567891234.0; System.out.println(x); System.out.println(y); BigDecimal bigZ = new BigDecimal(x).multiply(new BigDecimal(y)); double z = bigZ.doubleValue(); System.out.println(bigZ); System.out.println(z); 

Resultados:

 1.234567891234E12 //precise 'x' 1.234567891234E12 //precise 'y' 1524157878065965654042756 //precise 'x * y' 1.5241578780659657E24 //loosing precision 

y são precisos, assim como a multiplicação usando BigDecimal . No entanto, depois de voltar a double perdemos menos dígitos significativos.

Eu também recomendo que você use nada além de BigDecimal para toda a aritmética que pode envolver moeda.

Certifique-se de sempre usar o construtor String de BigDecimal. Por quê? Tente o seguinte código em um teste JUnit:

 assertEquals(new BigDecimal("0.01").toString(), new BigDecimal(0.01).toString()); 

Você obtém a seguinte saída:

 expected:<0.01[]> but was <0.01[000000000000000020816681711721685132943093776702880859375]> 

A verdade é que você não pode armazenar EXATAMENTE 0,01 como um valor “duplo”. Somente BigDecimal armazena o número que você precisa EXATAMENTE como você quer.

E lembre-se que o BigDecimal é imutável. O seguinte irá compilar:

 BigDecimal amount = new BigDecimal("123.45"); BigDecimal more = new BigDecimal("12.34"); amount.add(more); System.out.println("Amount is now: " + amount); 

mas a saída resultante será:

A quantia é agora: 123,45

Isso porque você precisa atribuir o resultado a uma nova (ou a mesma) variável BigDecimal.

Em outras palavras:

 amount = amount.add(more) 

long será uma escolha muito melhor do que o double / float .

Você tem certeza de que usar o tipo BigDecimal será um gargalo real?

O que é aceitável depende do seu projeto. Você pode usar o dobro e o longo em alguns projetos que podem ser esperados para isso. No entanto, em outros projetos, isso é considerado inaceitável. Como um dobro você pode representar valores até 70.000.000.000.000,00 ao centavo (maior que a dívida nacional dos EUA), com lugar fixo longo você pode representar 90.000.000.000.000.000,00 com precisão.

Se você tem que lidar com moedas hiperinflacionárias (uma má idéia em qualquer caso), mas por algum motivo ainda precisa contabilizar cada centavo, use BigDecimal.

Se você usar double ou long ou BigDecimal, você deve arredondar o resultado. A maneira como você faz isso varia de acordo com cada tipo de dados e BigDecimal é a menos propensa a erros, já que você precisa especificar o arredondamento e a precisão de diferentes operações. Com o dobro ou longo, você é deixado para seus próprios dispositivos.

Pit fall é que floats / doubles não podem armazenar todos os valores sem perder precisão. Mesmo se você usar BigDecimal e preservar a precisão durante os cálculos, ainda estará armazenando o produto final como float / double.

A solução “adequada” para isso, na minha experiência, é armazenar valores monetários como números inteiros (por exemplo, Long ) representando milhares de dólares. Isso dá resolução suficiente para a maioria das tarefas, por exemplo, acréscimo de juros, enquanto o lado de pisar o problema de usar floats / duplas. Como um “bônus” adicional, isso requer aproximadamente a mesma quantidade de armazenamento que floats / duplas.

Se o único uso de double é armazenar valores decimais, então sim, você pode sob algumas condições: se puder garantir que seus valores não tenham mais de 15 dígitos decimais, então converta um valor para double (53 bits de precisão) e convertendo o double volta ao decimal com precisão de 15 dígitos (ou menos) lhe dará o valor original, ou seja, sem qualquer perda, de uma aplicação do teorema de David Matula provado em seu artigo In-and-out conversions . Note que para este resultado ser aplicável, as conversões devem ser feitas com o arredondamento correto .

Note, no entanto, que um double pode não ser a melhor escolha: valores monetários são geralmente expressos não em ponto flutuante, mas em ponto fixo com alguns dígitos ( p ) após o ponto decimal, e neste caso, convertendo o valor em um inteiro com um dimensionamento de 10 ^ p e armazenar esse inteiro (como os outros sugeriram) é melhor.