The following simple Groovy script is based on the Java class in the previous blog post, but it is now "Groovier" and has a few more things demonstrated.
//import java.math.BigDecimal;
/**
* Simple example of problems associated with using BigDecimal constructor
* accepting a double.
*
* http://marxsoftware.blogspot.com/
*/
NEW_LINE = System.getProperty("line.separator")
//
// Demonstrate BigDecimal from double
//
double primitiveDouble = 0.1
BigDecimal bdPrimDoubleCtor = new BigDecimal(primitiveDouble)
BigDecimal bdPrimDoubleValOf = BigDecimal.valueOf(primitiveDouble)
Double referenceDouble = Double.valueOf(0.1)
BigDecimal bdRefDoubleCtor = new BigDecimal(referenceDouble)
BigDecimal bdRefDoubleValOf = BigDecimal.valueOf(referenceDouble)
println "Primitive Double: ${primitiveDouble}"
println "Reference Double: ${referenceDouble}"
println "Primitive BigDecimal/Double via Double Ctor: ${bdPrimDoubleCtor}"
println "Reference BigDecimal/Double via Double Ctor: ${bdRefDoubleCtor}"
println "Primitive BigDecimal/Double via ValueOf: ${bdPrimDoubleValOf}"
println "Reference BigDecimal/Double via ValueOf: ${bdRefDoubleValOf}"
println NEW_LINE
//
// Demonstrate BigDecimal from float
//
float primitiveFloat = 0.1f
BigDecimal bdPrimFloatCtor = new BigDecimal(primitiveFloat)
BigDecimal bdPrimFloatValOf = BigDecimal.valueOf(primitiveFloat)
Float referenceFloat = Float.valueOf(0.1f)
BigDecimal bdRefFloatCtor = new BigDecimal(referenceFloat)
BigDecimal bdRefFloatValOf = BigDecimal.valueOf(referenceFloat)
print "Primitive Float: ${primitiveFloat}"
println " (${primitiveFloat.class})"
print "Reference Float: ${referenceFloat}"
println " (${referenceFloat.class})"
print "Primitive BigDecimal/Float via Double Ctor: ${bdPrimFloatCtor}"
println " (${bdPrimFloatCtor.class})"
print "Reference BigDecimal/Float via Double Ctor: ${bdRefFloatCtor}"
println " (${bdRefFloatCtor.class})"
print "Primitive BigDecimal/Float via ValueOf: ${bdPrimFloatValOf}"
println " (${bdPrimFloatValOf.class})"
print "Reference BigDecimal/Float via ValueOf: ${bdRefFloatValOf}"
println " (${bdRefFloatValOf.class})"
println NEW_LINE
//
// More evidence of issues casting from float to double.
//
double primitiveDoubleFromFloat = 0.1f
Double referenceDoubleFromFloat = new Double(0.1f)
double primitiveDoubleFromFloatDoubleValue = new Float(0.1f).doubleValue()
print "Primitive Double from Float: ${primitiveDoubleFromFloat}"
println " (${primitiveDoubleFromFloat.class})"
print "Reference Double from Float: ${referenceDoubleFromFloat}"
println " (${referenceDoubleFromFloat.class}"
print "Primitive Double from FloatDoubleValue: ${primitiveDoubleFromFloatDoubleValue}"
println " (${primitiveDoubleFromFloatDoubleValue.class})"
println NEW_LINE
//
// Using String to maintain precision from float to BigDecimal
//
String floatString = String.valueOf(new Float(0.1f))
BigDecimal bdFromFloatViaString = new BigDecimal(floatString)
print "BigDecimal from Float via String.valueOf(): ${bdFromFloatViaString}"
println " (${bdFromFloatViaString.class})"
println NEW_LINE
//
// Using "duck typing"
//
def decimalWithoutStaticType = 0.1
print "Decimal Without Static Type: ${decimalWithoutStaticType}"
println " (${decimalWithoutStaticType.class})"
def floatWithoutStaticType = 0.1f
print "Float Without Static Type: ${floatWithoutStaticType}"
println " (${floatWithoutStaticType.class})"
def explicitDoubleWithoutStaticType = 0.1d
print "Explicit Double Without Static Type: ${explicitDoubleWithoutStaticType}"
println " (${explicitDoubleWithoutStaticType.class})"
The Groovier script shown above demonstrates that there is no need in Groovy to explicitly import java.math.BigDecimal. The script also has several of the classes's Class definitions printed out to show how Groovy treats these classes.
The following output is generated when this script is executed.
There are several observations one can make from this output. One, Groovy allows variables to be statically typed. When they are statically typed, they are essentially treated exactly as they are in Java with precision loss and all. Two, Groovy's dynamic typing automatically applies BigDecimal to floating-point numbers if no specific type is specified. Three, as the last few lines of the output demonstrate, Groovy prints out the value of 0.1 properly even when it reports Float, Double, or BigDecimal. In other words, the value of 0.1 is treated consistently as exactly 0.1 as long as the static types of float and double were not explicitly specified when declaring the variable. Now that's nice.
Conclusion
Groovy simplifies many of the nuances of floating-point arithmetic and representation. The Groovy Math documentation page calls Groovy's approach to mathematical operations a "least surprising" approach. That's something we can all appreciate.
Additional Resources
The following online resources contain further information about BigDecimal and Groovy.
⇒ Getting Around BigDecimal Pain with Groovy
⇒ The Evil BigDecimal Constructor
⇒ Make Cents with BigDecimal
⇒ The Need for BigDecimal
⇒ Why is the BigDecimal(double) Construction Still Around?
No comments:
Post a Comment