Saurabh Gaur Saurabh Gaur - 2 months ago 9
Groovy Question

Confusion using with double value, when groovy decides to make value in double vs BigDecimal?

I'm little bit confused using

double
value.

When I used as below :-

double foo = 20.46455
assert 20 == foo.round()
assert 20.46 == foo.round(2)


It's working fine. but when I used something like as :-

def foo = 20.46455
assert 20 == foo.round()


it throws :-


java.lang.NullPointerException


and

def foo = 20.46455
assert 20.46 == foo.round(2)


it throws :-


groovy.lang.MissingMethodException: No signature of method: java.math.BigDecimal.round() is applicable for argument types: (java.lang.Integer) values: [2]
Possible solutions: round(java.math.MathContext), find(), pow(int), power(java.lang.Integer), find(groovy.lang.Closure), and(java.lang.Number)


It means by default in
groovy
, value preserve in
BigDecimal
and
BigDecimal.round()
expect
java.math.MathContext
as input.

But My confusion start when I'm using
Math.round()
which except
double
as input then why below statement is getting passed while groovy preserve by default in
BigDecimal
?

def foo = 20.46455
assert 20 == Math.round(foo)


And why I have to use
.toDouble()
to pass my test case while
foo
has value in
double
format as below?

def foo = 20.46455
assert 20 == foo.toDouble().round()
assert 20.46 == foo.toDouble().round(2)


Note :- I don't want to know how to
round
a double value, I just want to know why
groovy
behaves differently in each case??

Answer

Groovy automatically and implicitly uses BigDecimal for any floating-point numbers unless you define a type or you add a suffix for the number (like D).

Examples:

def foo = 20.46455
println foo.getClass()

Output:

class java.math.BigDecimal

double foo = 20.45645
println foo.getClass()

Output:

class java.lang.Double

def foo = 20.45645d
println foo.getClass()

Output:

class java.lang.Double

Type conversions:

Groovy also has certain automatic type conversions and this is the reason why even though Math.round() only accepts double and float primitives as parameters, the code is not failing when you pass a BigDecimal. To prove this, you could implement your own round function and check what happens with the type conversions:

Examples:

def round(double foo) {
   println foo.getClass()
   return foo.round()
}

def foo = 20.46455
println foo.getClass()
assert 20 == round(foo)

Output:

class java.math.BigDecimal

class java.lang.Double

Some more valid examples of implicit conversions:

def round(float foo) {
   println foo.getClass()
   return foo.round()
}

def foo = 20.46455
println foo.getClass()
assert 20 == round(foo)

Output:

class java.math.BigDecimal

class java.lang.Float

def round(float foo) {
   println foo.getClass()
   return foo.round()
}

def foo = 20
println foo.getClass()
assert 20 == round(foo)

Output:

class java.lang.Integer

class java.lang.Float

def round(double foo) {
   println foo.getClass()
   return foo.round()
}

def foo = 20
println foo.getClass()
assert 20 == round(foo)

Output:

class java.lang.Integer

class java.lang.Double

def round(BigDecimal foo) {   
   println foo.getClass()
   return foo
}

double foo = 20.0
println foo.getClass()
assert 20 == round(foo)

‚Äč Output:

class java.lang.Double

class java.lang.BigDecimal

As a rule of thumb, if the number is floating point based (double, float, BigDecimal) there will be an implicit type conversion between each other, and the code will throw an exception when trying to convert to non-floating point numbers (like int or long). If a number is not a floating point type (int, long), it can be converted between non-floating and floating point types, as floating point numbers also include non-floating points as a subset (e.g. 1 can be represented with 1.0). This makes sense, as you can't pass the floating point information from a float to an int (20.5 can't be represented with an int variable), but in most cases you can do the opposite, with the occasional exception of overflows for big values (e.g a really big long number into a float variable).