Sumon Rahman
Sumon Rahman

Reputation: 99

RoundingMode.HALF_UP difference in scala and Java

In scala, I am trying to round up the following number to the 3 decimal precision using HALF_UP rounding mode. 8409.3555

Unfortunately scala is returning 8409.356, but Java is returning 8409.355

Scala code:

def round(d: Double): Double = {
    BigDecimal.apply(d).setScale(3, RoundingMode.HALF_UP).doubleValue()
}

Java code:

private static Double round(Double d) {
    BigDecimal bd = new BigDecimal(d);
    bd = bd.setScale(3, RoundingMode.HALF_UP);
    return bd.doubleValue();
}

Is there any known issue?

Upvotes: 4

Views: 5395

Answers (2)

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22595

I could also reproduce this issue.

def roundWithScala(d: Double): Double = {
    BigDecimal(d).setScale(3, RoundingMode.HALF_UP).doubleValue()
}

def roundWithJava(d: Double): Double = {
    val bd = new java.math.BigDecimal(d).setScale(3, java.math.RoundingMode.HALF_UP)
    return bd.doubleValue()
}

println(roundWithScala(8407.3555)) //8407.356
println(roundWithJava(8407.3555)) //8407.355

println(roundWithScala(8409.3555)) //8409.356
println(roundWithJava(8409.3555)) //8409.355

println(roundWithScala(8409.4555)) //8409.456
println(roundWithJava(8409.4555)) //8409.456

First thing I noticed is that Scala is using diffrent MathContext than Java.

I tried using same MathContext.UNLIMITED in both, but it didn't changed anything.

Then I noticed that in scala's BigDecimal java`s BigDecimal is constructed like this:

new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc)

I tried using it in Java:

 val bd = new java.math.BigDecimal(java.lang.Double.toString(d), java.math.MathContext.UNLIMITED).setScale(3, java.math.RoundingMode.HALF_UP)

and I received same result (8409.356).

So basically the reason why you get diffrent result's is that double is transformed to string in scala's constructor.

Maybe you could use java's BigDecimal in your case (as I did in roundWithJava)?

Upvotes: 1

Michael Zajac
Michael Zajac

Reputation: 55569

In general, you don't want to convert literal doubles to BigDecimal directly, because you may be bitten by floating point rounding errors. There is also a warning in the scaladoc:

When creating a BigDecimal from a Double or Float, care must be taken as the binary fraction representation of Double and Float does not easily convert into a decimal representation

We can see it happen with java.math.BigDecimal:

scala> new java.math.BigDecimal(8409.3555)
res1: java.math.BigDecimal = 8409.355499999999665305949747562408447265625

When you try to round that number (half) up, it is now 8409.355. scala.math.BigDecimal.apply uses a MathContext to round the Double passed to apply immediately, so that the resulting BigDecimal has an increased chance of having the same value as the literal Double you passed in. Within the first snippet, what's really being called is:

scala> scala.math.BigDecimal(8409.3555)(java.math.MathContext.DECIMAL128)
res10: scala.math.BigDecimal = 8409.3555

It would be better to represent these literals as strings to avoid any rounding errors from storing the Double. For example:

scala> scala.math.BigDecimal("8409.3555")
res17: scala.math.BigDecimal = 8409.3555

scala> new java.math.BigDecimal("8409.3555")
res18: java.math.BigDecimal = 8409.3555

If you must convert from Double, I'd suggest using scala.math.BigDecimal.apply to do so.

Upvotes: 3

Related Questions