Attenzione: Double to BigDecimal in Java

La combinazione dell'ampia base di sviluppatori di Java in tutto il mondo e della documentazione API in linea facilmente accessibile ha portato a una documentazione generalmente completa e accurata dell'API Java SE. Ci sono ancora angoli che potrebbero non essere così completi o accurati come si vorrebbe, ma la documentazione API è generalmente abbastanza buona sia in termini di completezza che di accuratezza.

Sebbene la documentazione API basata su Javadoc sia diventata piuttosto utile, noi sviluppatori siamo spesso così di fretta e spesso ci sentiamo così sicuri delle nostre capacità che è quasi inevitabile che a volte continueremo a provare a fare le cose senza prima leggere il manuale. A causa di questa tendenza, possiamo occasionalmente scottarci facendo un uso improprio di una particolare API nonostante la documentazione ci avverta di non usarla (male) in questo modo. Ne ho discusso nel mio post sul blog su Boolean.getBoolean (String) e in questo post ho evidenziato un problema simile relativo all'uso del costruttore di BigDecimal che accetta un double.

A prima vista, potrebbe sembrare che il costruttore BigDecimal che accetta un double Java lo mantenga con la sua precisione originariamente specificata in tutti i casi. Tuttavia, il messaggio Javadoc per questo costruttore avverte esplicitamente: "I risultati di questo costruttore possono essere alquanto imprevedibili". Continua spiegando perché (il double non può mantenere la precisione esatta e questo è reso evidente quando viene passato al costruttore BigDecimal) e suggerendo di utilizzare invece il costruttore alternativo che accetta una String come parametro. La documentazione propone inoltre di utilizzare BigDecimal.valueOf (double) come metodo preferito per convertire un double o un float in un BigDecimal.

L'elenco di codice seguente viene utilizzato per dimostrare questi principi e alcune idee correlate.

DoubleToBigDecimal.java

import java.math.BigDecimal; import static java.lang.System.out; /** * Simple example of problems associated with using BigDecimal constructor * accepting a double. * * //marxsoftware.blogspot.com/ */ public class DoubleToBigDecimal { private final static String NEW_LINE = System.getProperty("line.separator"); public static void main(final String[] arguments) { // // Demonstrate BigDecimal from double // final double primitiveDouble = 0.1; final BigDecimal bdPrimDoubleCtor = new BigDecimal(primitiveDouble); final BigDecimal bdPrimDoubleValOf = BigDecimal.valueOf(primitiveDouble); final Double referenceDouble = Double.valueOf(0.1); final BigDecimal bdRefDoubleCtor = new BigDecimal(referenceDouble); final BigDecimal bdRefDoubleValOf = BigDecimal.valueOf(referenceDouble); out.println("Primitive Double: " + primitiveDouble); out.println("Reference Double: " + referenceDouble); out.println("Primitive BigDecimal/Double via Double Ctor: " + bdPrimDoubleCtor); out.println("Reference BigDecimal/Double via Double Ctor: " + bdRefDoubleCtor); out.println("Primitive BigDecimal/Double via ValueOf: " + bdPrimDoubleValOf); out.println("Reference BigDecimal/Double via ValueOf: " + bdRefDoubleValOf); out.println(NEW_LINE); // // Demonstrate BigDecimal from float // final float primitiveFloat = 0.1f; final BigDecimal bdPrimFloatCtor = new BigDecimal(primitiveFloat); final BigDecimal bdPrimFloatValOf = BigDecimal.valueOf(primitiveFloat); final Float referenceFloat = Float.valueOf(0.1f); final BigDecimal bdRefFloatCtor = new BigDecimal(referenceFloat); final BigDecimal bdRefFloatValOf = BigDecimal.valueOf(referenceFloat); out.println("Primitive Float: " + primitiveFloat); out.println("Reference Float: " + referenceFloat); out.println("Primitive BigDecimal/Float via Double Ctor: " + bdPrimFloatCtor); out.println("Reference BigDecimal/Float via Double Ctor: " + bdRefFloatCtor); out.println("Primitive BigDecimal/Float via ValueOf: " + bdPrimFloatValOf); out.println("Reference BigDecimal/Float via ValueOf: " + bdRefFloatValOf); out.println(NEW_LINE); // // More evidence of issues casting from float to double. // final double primitiveDoubleFromFloat = 0.1f; final Double referenceDoubleFromFloat = new Double(0.1f); final double primitiveDoubleFromFloatDoubleValue = new Float(0.1f).doubleValue(); out.println("Primitive Double from Float: " + primitiveDoubleFromFloat); out.println("Reference Double from Float: " + referenceDoubleFromFloat); out.println("Primitive Double from FloatDoubleValue: " + primitiveDoubleFromFloatDoubleValue); // // Using String to maintain precision from float to BigDecimal // final String floatString = String.valueOf(new Float(0.1f)); final BigDecimal bdFromFloatViaString = new BigDecimal(floatString); out.println("BigDecimal from Float via String.valueOf(): " + bdFromFloatViaString); } } 

L'output dell'esecuzione del codice precedente viene mostrato nell'istantanea della schermata successiva.

Come indica l'output sopra, il problema del casting float to double impedisce di mantenere la precisione desiderata quando si passa un float direttamente al BigDecimal.valueOf(double)metodo. Una stringa può essere utilizzata come intermediario per eseguire ciò mostrato nell'esempio e come dimostrato in modo simile in Conversione da virgola mobile a doppia in un modo non così comune.

Si noti che il pesante uso implicito di Groovy di BigDecimal cambia un po 'il gioco quando si utilizza Groovy e la digitazione dinamica. Potrei toccarlo in un futuro post sul blog. Per maggiori dettagli sulle questioni in virgola mobile (e sottolineo i "dettagli"), vedere Cosa dovrebbe sapere ogni informatico sull'aritmetica in virgola mobile.

Questa storia, "Attenzione: Double to BigDecimal in Java" è stata originariamente pubblicata da JavaWorld.