Although the Javadoc-based API documentation has become pretty useful, we developers are often in such a hurry and often feel so confident in our own abilities that it is almost inevitable that we will sometimes continue to try to do things without first reading the manual. Because of this tendency, we can occasionally get burned by misusing a particular API despite the documentation warning us not to (mis)use it that way. I discussed this in my blog post on Boolean.getBoolean(String) and highlight a similar issue in this post related to use of BigDecimal's constructor that accepts a double.
At first sight, it might appear that the BigDecimal constructor that accepts a Java double would hold it with its originally specified precision in all cases. However, the Javadoc message for this constructor explicitly warns, "The results of this constructor can be somewhat unpredictable." It goes on to explain why (the double cannot hold the exact precision and this is made evident when passed to the BigDecimal constructor) and to suggest that the alternative constructor accepting a String as a parameter be used instead. The documentation also proposes using BigDecimal.valueOf(double) as the preferred way to convert a double or float to a BigDecimal.
The following code listing is used to demonstrate these principles and a few related ideas.
DoubleToBigDecimal.java
import java.math.BigDecimal;
import static java.lang.System.out;
/**
* Simple example of problems associated with using BigDecimal constructor
* accepting a double.
*
* http://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);
}
}
The output from running the above code is shown in the next screen snapshot.
As the output above indicates, the problem of casting float to double prevents one from retaining the desired precision when passing a float directly to the
BigDecimal.valueOf(double)
method. A String can be used as an intermediary to accomplish this shown in the example and as demonstrated in similar fashion in Converting Float to Double in a Not So Common Way.Note that Groovy's heavy implicit use of BigDecimal changes the game a little bit when using Groovy and dynamic typing. I may touch on that in a future blog post. For more details on floating-point issues (and I emphasize "details"), see What Every Computer Scientist Should Know About Floating-Point Arithmetic.
No comments:
Post a Comment