5

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??

Saurabh Gaur
  • 23,507
  • 10
  • 54
  • 73

1 Answers1

11

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).

  • Then why it [`def foo = 20.46455;assert 20 == Math.round(foo)`](https://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round(double)) get passed?? – Saurabh Gaur Sep 09 '16 at 12:39
  • `def foo = 20.46455` lets Groovy decide what type this variable has using type inference, and it chooses BigDecimal. `double foo = 20.46455` forces the variable to be a `double` type. You could alternatively use a suffix to force the `double` declaration like this `def foo = 20.46455D` – Jose Ignacio Acin Pozo Sep 09 '16 at 12:42
  • But it get passed without forcing to `double`, that's why I'm confused, I'm using only `double foo = 20.46455` and groovy automatically takes it as `double` when use it as `assert 20 == Math.round(foo)` but when I used it as `foo.round()` groovy takes it as `BigDecimal` and it throws exception... – Saurabh Gaur Sep 09 '16 at 12:47
  • 1
    Sorry, I see what you mean now. Groovy also has automatic type conversions. Let me extend my answer to include this. – Jose Ignacio Acin Pozo Sep 09 '16 at 12:53
  • +1 Yes, it makes little bit sense. But how groovy decides when it will use as `double` and when `BigDecimal`??? is this decided on the basis of method refrence?? – Saurabh Gaur Sep 09 '16 at 13:37
  • 1
    If the object itself is a BigDecimal `def foo = 20.46455`, and you try to call a method which is not implemented for a BigDecimal (like `round()`) it will throw an exception. But if you are passing it as a parameter, Groovy will try to accomodate the type (like `BigDecimal` passed to a method expecting a `double` or a `float` parameter), but it will still throw an exception if the conversion is not possible. I will extend a bit more my answer to show more implicit conversions. – Jose Ignacio Acin Pozo Sep 09 '16 at 14:28
  • Thanks for your input, really appreciate..:) – Saurabh Gaur Sep 09 '16 at 15:06
  • Happy I could help! If this solves all your questions could set it as the right answer? Thanks! – Jose Ignacio Acin Pozo Sep 09 '16 at 16:33