Potential bug in scala.math.BigDecimal when converting null j.m.BigDecimal ?

1,114 views
Skip to first unread message

Fernando Racca

unread,
Feb 3, 2014, 4:45:41 PM2/3/14
to scala...@googlegroups.com
Details posted in SO question http://stackoverflow.com/questions/21527555/comparing-null-bigdecimal-from-java-bigdecimal

Can somebody verify that this is indeed a bug or should I just raise it in the JIRA directly ?

The suggested workaround converting j.m.BigDecimal to Option[BigDecimal] does the trick

Thanks
Fernando Racca

Rex Kerr

unread,
Feb 3, 2014, 4:53:59 PM2/3/14
to Fernando Racca, scala-user
In 2.11, wrapping null in Scala's BigDecimal will not be allowed.  You'll get an exception when you try.  I think this will "solve" all the problems.  (null is not a valid numeric value anyway.)

  --Rex


Fernando Racca

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Fernando Racca

unread,
Feb 3, 2014, 5:19:04 PM2/3/14
to scala...@googlegroups.com, Fernando Racca


On Monday, 3 February 2014 21:53:59 UTC, Rex Kerr wrote:
In 2.11, wrapping null in Scala's BigDecimal will not be allowed.  You'll get an exception when you try. 

How will that work?

 
I think this will "solve" all the problems.  (null is not a valid numeric value anyway.)

  --Rex


null is clearly not a numeric value, but , to put an example, when dealing with domain object classes in Java that bring data from a database, somehow we need to convert this data back. The existing conversions incorrectly produce a BigDecimal(null).

Even if 2.11 has a guard against it, it's still possible in 2.10.

 Fernando

Rex Kerr

unread,
Feb 3, 2014, 5:30:52 PM2/3/14
to Fernando Racca, scala-user
On Mon, Feb 3, 2014 at 2:19 PM, Fernando Racca <fra...@gmail.com> wrote:



On Monday, 3 February 2014 21:53:59 UTC, Rex Kerr wrote:
In 2.11, wrapping null in Scala's BigDecimal will not be allowed.  You'll get an exception when you try. 

How will that work?

It throws an IllegalArgumentException (IIRC) as soon as you try to create a scala.math.BigDecimal with a null value.
 

 
I think this will "solve" all the problems.  (null is not a valid numeric value anyway.)

  --Rex


null is clearly not a numeric value, but , to put an example, when dealing with domain object classes in Java that bring data from a database, somehow we need to convert this data back. The existing conversions incorrectly produce a BigDecimal(null).

If you need to correctly handle null values coming in from a database, I suggest you use some other strategy than creating something that looks like a number but explodes whenever you try to use it as one.  Option[BigDecimal] comes to mind:

  Option(javaDecimal).map(x => x: BigDecimal)

You would never have gotten very far doing math on a null value anyway.

  --Rex



David Landis

unread,
Feb 3, 2014, 5:41:51 PM2/3/14
to Rex Kerr, Fernando Racca, scala-user
On Mon, Feb 3, 2014 at 5:30 PM, Rex Kerr <ich...@gmail.com> wrote:
On Mon, Feb 3, 2014 at 2:19 PM, Fernando Racca <fra...@gmail.com> wrote:


On Monday, 3 February 2014 21:53:59 UTC, Rex Kerr wrote:
In 2.11, wrapping null in Scala's BigDecimal will not be allowed.  You'll get an exception when you try. 

How will that work?

It throws an IllegalArgumentException (IIRC) as soon as you try to create a scala.math.BigDecimal with a null value.

Should `BigInt` be made consistent in that respect as well?

Rex Kerr

unread,
Feb 3, 2014, 5:50:22 PM2/3/14
to David Landis, Fernando Racca, scala-user
Yes, it should.  If you could be so kind as to file a bug report, I will fix it.  If you don't, I probably will anyway, but might forget.

  --Rex


--

Fernando Racca

unread,
Feb 3, 2014, 6:32:58 PM2/3/14
to Rex Kerr, scala-user
I was expecting that the implicit conversions will respect a null value if that's what the field been converted holds. Instead, it'll try to wrap it corrupting the state. Then one ends up with something worse than null, a null number wrapper object. 

To use Option then is not just beneficial, but mandatory whenever dealing with existing Java projects. 

IMHO this is a bug, and quite severe, since not only causes unexpected behaviour, but the workaround involves importing yet another implicit conversions, further polluting the code base.


Rex Kerr

unread,
Feb 3, 2014, 7:02:07 PM2/3/14
to Fernando Racca, scala-user
On Mon, Feb 3, 2014 at 3:32 PM, Fernando Racca <fra...@gmail.com> wrote:
I was expecting that the implicit conversions will respect a null value if that's what the field been converted holds. Instead, it'll try to wrap it corrupting the state. Then one ends up with something worse than null, a null number wrapper object. 

To use Option then is not just beneficial, but mandatory whenever dealing with existing Java projects. 

IMHO this is a bug, and quite severe, since not only causes unexpected behaviour, but the workaround involves importing yet another implicit conversions, further polluting the code base.

Unfortunately, when you are stuck with input that has lower safety levels than you'd like, something has got to give somewhere.

Requiring that the wrapper object also be null simply propagates that lack of safety deeper into the library.  One of the nice features of working with Scala is that such things are almost all taken care of at the interfaces, so you don't have to worry about null checks within Scala code.  This is the idiomatic way to deal with it.  Certainly a null -> null conversion shouldn't be the only thing one can do with an implicit conversion, since you might be converting to something like Option where the whole goal is to catch the null and replace it with something more prominent that will keep you from forgetting that it might be null (while giving you tools to deal with it).

If you want to keep propagating nulls, I recommend something like

  implicit class BDKeepsNulls(val repr: java.math.BigDecimal) extends AnyVal {
    def asScala: BigDecimal = if (repr eq null) null else new BigDecimal(repr)
  }

and manually annotate all the Java inputs with .asScala.

Annoying, yes, but in the medium term, I think this will pay off due to increased transparency.

In the longer run, I would investigate a strategy for not using null to keep track of absence of a value.

  --Rex
 

David Landis

unread,
Feb 3, 2014, 11:37:46 PM2/3/14
to Rex Kerr, Fernando Racca, scala-user

Fernando Racca

unread,
Feb 4, 2014, 3:09:34 AM2/4/14
to scala...@googlegroups.com, Fernando Racca
I know that implicits can be used to solve the effect, but ultimately now one needs to use implicits all over to deal with a null value. Perhaps one would like to keep that null value in some places, and only convert it when necessary

I tested this behaviour against 2.11-M8.

To my surprise, it goes even further:

scala> import java.math.{BigDecimal => JBigDecimal}

import java.math.{BigDecimal=>JBigDecimal}

scala> val x : JBigDecimal = null

x: java.math.BigDecimal = null

scala> val y : BigDecimal = null

y: BigDecimal = null

scala> val y : BigDecimal = x

java.lang.IllegalArgumentException: null value for BigDecimal

  at scala.math.BigDecimal.<init>(BigDecimal.scala:406)

  at scala.math.BigDecimal$.apply(BigDecimal.scala:334)

  at scala.math.BigDecimal$.apply(BigDecimal.scala:331)

  at scala.math.BigDecimal$.javaBigDecimal2bigDecimal(BigDecimal.scala:346)

  ... 43 elided

scala> lazy val y : BigDecimal = x

y: BigDecimal = <lazy>

scala> x == y

java.lang.IllegalArgumentException: null value for BigDecimal

  at scala.math.BigDecimal.<init>(BigDecimal.scala:406)

  at scala.math.BigDecimal$.apply(BigDecimal.scala:334)

  at scala.math.BigDecimal$.apply(BigDecimal.scala:331)

  at scala.math.BigDecimal$.javaBigDecimal2bigDecimal(BigDecimal.scala:346)

  at .y$lzycompute(<console>:9)

  at .y(<console>:9)

  ... 43 elided


scala> import java.lang.{Double => JDouble}

import java.lang.{Double=>JDouble}

scala> val e : JDouble = null

e: Double = null

scala> val f : Double = e

java.lang.NullPointerException

  at scala.Predef$.Double2double(Predef.scala:352)

  ... 43 elided


scala> lazy val f = e

f: Double = <lazy>

scala> e == f

res2: Boolean = true


In 2.10.3, you can convert a  null JDouble to Double without an exception

Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_40).

scala> import java.math.{BigDecimal => JBigDecimal}

import java.math.{BigDecimal=>JBigDecimal}

scala> val x : JBigDecimal = null

x: java.math.BigDecimal = null

scala> val y : BigDecimal = x

java.lang.NullPointerException

at scala.math.BigDecimal.toString(BigDecimal.scala:452)

at scala.runtime.ScalaRunTime$.scala$runtime$ScalaRunTime$$inner$1(ScalaRunTime.scala:324)

at scala.runtime.ScalaRunTime$.stringOf(ScalaRunTime.scala:329)

at scala.runtime.ScalaRunTime$.replStringOf(ScalaRunTime.scala:337)

at .<init>(<console>:10)

at .<clinit>(<console>)

at $print(<console>)


scala> lazy val y : BigDecimal = x

y: BigDecimal = <lazy>

scala> x == y

java.lang.NullPointerException

at scala.math.BigDecimal.toLongExact(BigDecimal.scala:411)

at scala.math.BigDecimal$$anonfun$isValidLong$1.apply$mcV$sp(BigDecimal.scala:196)

at scala.math.BigDecimal.noArithmeticException(BigDecimal.scala:211)

at scala.math.BigDecimal.isValidLong(BigDecimal.scala:196)

at scala.math.BigDecimal.equals(BigDecimal.scala:190)

at scala.runtime.BoxesRunTime.equalsNumNum(BoxesRunTime.java:168)

at .<init>(<console>:11)

at .<clinit>(<console>)

at .<init>(<console>:7)

at .<clinit>(<console>)

at $print(<console>)


scala> import java.lang.{Double => JDouble} 

import java.lang.{Double=>JDouble}

scala> val e : JDouble = null

e: Double = null

scala> val f = e

f: Double = null

scala> e == f

res1: Boolean = true


So two null doubles are equal, but two bigdecimals are not. 

The default conversion should try its best to give you a sensible value. If nothing concrete can be found, leaving it as it is, null, is acceptable. converting it to something else, but internally null, isn't.

This also means there are slightly more issues when migrating to 2.11M8, since assigning a null JDouble threw an NPE when printed (didn't use to in 2.10.3)

They are all runtime safety issues, but issues still.

Fernando

Dániel Fritsi

unread,
Mar 13, 2015, 3:28:41 PM3/13/15
to scala...@googlegroups.com, fra...@gmail.com
Are you kidding me??!! So in scala 2.10 we have a big fckn invalid object when we implicit convert a java BigDecimal to a scala one, and in 2.11 instead of fixing this you throw an Exception into our face??!!
This is unacceptable. I think you should fix the implicit conversion to return a null if a null is passed!

Rex Kerr

unread,
Mar 13, 2015, 5:05:53 PM3/13/15
to Dániel Fritsi, scala-user, Fernando Racca
You can calm down.  We've already decided that wrapped nulls are a worse evil than unwrapped ones and will have the safer versions in 2.12 despite the possibility of throwing new exceptions from apparently working code.  (Because that code is not actually working.)  Patches are already in for the Java collections conversions.  Since we're doing that for Java collections wrappers, to be consistent we'll (try to) do it for everything, including BigInt and BigDecimal.

However, if you're trying to wrap null objects, something is probably wrong.  The change to wrappers will let you kick the can down the road a bit, but eventually you've got to deal with the problem of not having a usable object.  When you're creating the wrapper with a constructor, you simply can't return a null object, so failing fast (with an exception) is likely preferable than creating a time bomb.

  --Rex

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Oliver Ruebenacker

unread,
Mar 13, 2015, 5:24:24 PM3/13/15
to Rex Kerr, Dániel Fritsi, scala-user, Fernando Racca

     Hello,

  Option[A] can sometimes be useful, but is not universally superior to a an A that can be null. Checking whether something is null or not is not universally worse than checking whether it is None or Some(a).

  The constructor can not return null, but every other method can.

     Best, Oliver
--
Oliver Ruebenacker
Solutions Architect at Altisource Labs
Be always grateful, but never satisfied.

Rex Kerr

unread,
Mar 13, 2015, 5:31:13 PM3/13/15
to Oliver Ruebenacker, Dániel Fritsi, scala-user, Fernando Racca
Mostly, if you want to play with null, you're left to your own devices.  Sure, you can do it.  For fully Scala library things, you need to catch your own nulls.  For example, foo.toList doesn't work if foo is null--you get a NPE.  No reason, then, that an extension method toBar shouldn't also NPE if foo is null.

Java is still quite fond of using null in place of options, though, so specifically when wrapping Java classes a best effort will be made to preserve the behavior.  Then it's the user's responsibility to keep track of which things could possibly be null.

(Would be great if the compiler could do most of the work for you, but right now it can't.)

  --Rex

Nils Kilden-Pedersen

unread,
Mar 13, 2015, 11:33:06 PM3/13/15
to Rex Kerr, Dániel Fritsi, scala-user, Fernando Racca
On Fri, Mar 13, 2015 at 4:05 PM, Rex Kerr <ich...@gmail.com> wrote:
You can calm down.  We've already decided that wrapped nulls are a worse evil than unwrapped ones and will have the safer versions in 2.12

Somewhat related, has this been considered for 2.12?

Rex Kerr

unread,
Mar 14, 2015, 2:57:50 AM3/14/15
to Nils Kilden-Pedersen, Dániel Fritsi, scala-user, Fernando Racca
My initial reaction is that we would rather have BigInt and BigDecimal hide some of the wrinkles of the Java counterparts, and forwarding/extra object creation is usually quite inexpensive compared to the cost of the underlying methods.  But I'll think about it more and/or discuss with others before deciding anything (if I am the one who is supposed to decide).

  --Rex
Reply all
Reply to author
Forward
0 new messages