Reputation: 99
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
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
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