Attention: doubler en BigDecimal en Java

La combinaison de l'importante base de développeurs mondiale de Java et de la documentation d'API en ligne facilement accessible a conduit à une documentation généralement complète et précise de l'API Java SE. Il y a encore des coins qui peuvent ne pas être aussi approfondis ou précis que l'on le souhaiterait, mais la documentation de l'API est généralement assez bonne en termes de rigueur et de précision.

Bien que la documentation de l'API basée sur Javadoc soit devenue assez utile, nous, les développeurs, sommes souvent si pressés et nous sentons souvent tellement confiants en nos propres capacités qu'il est presque inévitable que nous continuerons parfois à essayer de faire les choses sans avoir d'abord lu le manuel. En raison de cette tendance, nous pouvons parfois nous brûler en utilisant mal une API particulière malgré la documentation nous avertissant de ne pas (abuser) l'utiliser de cette façon. J'en ai discuté dans mon article de blog sur Boolean.getBoolean (String) et j'ai mis en évidence un problème similaire dans cet article lié à l'utilisation du constructeur de BigDecimal qui accepte un double.

À première vue, il peut sembler que le constructeur BigDecimal qui accepte un double Java le contiendrait dans tous les cas avec sa précision d'origine. Cependant, le message Javadoc pour ce constructeur avertit explicitement, "Les résultats de ce constructeur peuvent être quelque peu imprévisibles." Il continue en expliquant pourquoi (le double ne peut pas contenir la précision exacte et cela est rendu évident lorsqu'il est passé au constructeur BigDecimal) et à suggérer que le constructeur alternatif acceptant une String comme paramètre soit utilisé à la place. La documentation propose également d'utiliser BigDecimal.valueOf (double) comme moyen préféré de convertir un double ou un float en BigDecimal.

La liste de codes suivante est utilisée pour illustrer ces principes et quelques idées associées.

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

La sortie de l'exécution du code ci-dessus est affichée dans la capture d'écran suivante.

Comme l'indique la sortie ci-dessus, le problème de doubler le flotteur empêche de conserver la précision souhaitée lors du passage d'un flotteur directement à la BigDecimal.valueOf(double)méthode. Une chaîne peut être utilisée comme intermédiaire pour accomplir cela montré dans l'exemple et comme démontré de manière similaire dans la conversion de Float en double d'une manière pas si courante.

Notez que l'utilisation implicite intensive de BigDecimal par Groovy change un peu le jeu lors de l'utilisation de Groovy et de la saisie dynamique. Je pourrais en parler dans un prochain article de blog. Pour plus de détails sur les problèmes de virgule flottante (et j'insiste sur les «détails»), voir Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante.

Cette histoire, "Attention: doubler en BigDecimal en Java" a été initialement publiée par JavaWorld.