Precaución: Doble a BigDecimal en Java

La combinación de la gran base de desarrolladores mundial de Java y la documentación de API en línea de fácil acceso ha dado lugar a una documentación generalmente completa y precisa de la API de Java SE. Todavía hay rincones que pueden no ser tan completos o precisos como uno quisiera, pero la documentación de la API es generalmente bastante buena tanto en términos de minuciosidad como de precisión.

Aunque la documentación de la API basada en Javadoc se ha vuelto bastante útil, los desarrolladores a menudo tenemos tanta prisa y nos sentimos tan confiados en nuestras propias habilidades que es casi inevitable que a veces sigamos intentando hacer cosas sin leer primero el manual. Debido a esta tendencia, ocasionalmente podemos consumirnos si hacemos un mal uso de una API en particular a pesar de que la documentación nos advierte que no la usemos (mal) de esa manera. Hablé de esto en la publicación de mi blog en Boolean.getBoolean (String) y destaco un problema similar en esta publicación relacionado con el uso del constructor de BigDecimal que acepta un doble.

A primera vista, podría parecer que el constructor BigDecimal que acepta un doble de Java lo mantendría con su precisión originalmente especificada en todos los casos. Sin embargo, el mensaje Javadoc para este constructor advierte explícitamente, "Los resultados de este constructor pueden ser algo impredecibles". Continúa explicando por qué (el doble no puede contener la precisión exacta y esto se hace evidente cuando se pasa al constructor BigDecimal) y sugiere que en su lugar se use el constructor alternativo que acepta una cadena como parámetro. La documentación también propone usar BigDecimal.valueOf (double) como la forma preferida de convertir un doble o flotante en un BigDecimal.

La siguiente lista de códigos se utiliza para demostrar estos principios y algunas ideas relacionadas.

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); } } 

El resultado de ejecutar el código anterior se muestra en la siguiente captura de pantalla.

Como indica el resultado anterior, el problema de fundir el flotador al doble evita que uno retenga la precisión deseada cuando se pasa un flotador directamente al BigDecimal.valueOf(double)método. Una cadena se puede usar como intermediario para lograr esto que se muestra en el ejemplo y como se demuestra de manera similar en Conversión de flotante en doble de una manera no tan común.

Tenga en cuenta que el gran uso implícito de Groovy de BigDecimal cambia un poco el juego cuando se usa Groovy y la escritura dinámica. Puedo mencionar eso en una futura publicación de blog. Para obtener más detalles sobre cuestiones de punto flotante (y yo enfatizo los "detalles"), consulte Lo que todo científico informático debe saber sobre la aritmética de punto flotante.

Esta historia, "Precaución: Doble a BigDecimal en Java" fue publicada originalmente por JavaWorld.