
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
In this tutorial, we’ll see how to properly compare floating point numbers in Scala. Due to the way they’re stored, their value isn’t exact, so comparisons aren’t as easy as comparing integers or strings.
Floating point numbers are a prevalent source of bugs in programming. Unlike integer numbers, where 1 + 2 is always 3, with floating points this may not happen:
scala> 1 + 2
res0: Int = 3
scala> 0.1 + 0.2
res1: Double = 0.30000000000000004
This happens due to the very definition of how floating point numbers work in computer science.
So how should we compare floating points? Since arithmetic operations don’t produce exact results, this could cause bugs:
scala> val someMagicThreshold = 0.3
someMagicThreshold: Double = 0.3
scala> def checkThreshold(a: Double, b: Double): Boolean = {
| return a + b == someMagicThreshold
| }
checkThreshold: (a: Double, b: Double)Boolean
scala> checkThreshold(0.1, 0.2)
res0: Boolean = false
There are quite a few solutions possible such as:
Let’s dig more into the last one. The idea is straightforward: we give a small margin instead of doing an exact comparison. Even if the floating point arithmetic isn’t exact, the difference is usually small. The previous example returned 0.30000000000000004, which has a really small error:
def almostEqual(a: Double, b: Double, precision: Double): Boolean = {
(a - b).abs < precision
}
And now, we can use it:
scala> almostEqual(1.0, 1.001, 0.0001)
res1: Boolean = false
scala> almostEqual(1.0, 1.0001, 0.0001)
res2: Boolean = true
We can make the comparison even cleaner by using Scala implicit classes and defining a new operator:
scala>
class withAlmostEquals(d:Double) {
def ~=(y: Double, precision: Double) = (d - y).abs <= precision
}
scala> implicit def add_~=(d:Double) = new withAlmostEquals(d)
scala> 0.0 ~= (0.00001, 0.0001)
res0: Boolean = true
And as the last step, we can create an implicit precision to avoid passing the precision in every call and to ensure some consistency across the codebase:
scala> case class Precision(val p:Double)
defined class Precision
scala>
class withAlmostEquals(d:Double) {
def ~=(y: Double)(implicit precision:Precision) = (d - y).abs <= precision.p
}
defined class withAlmostEquals
scala> implicit def add_~=(d:Double) = new withAlmostEquals(d)
add_$tilde$eq: (d: Double)withAlmostEquals
scala> 0.0~=0.0
<console>:18: error: could not find implicit value for parameter precision: Precision
0.0~=0.0
^
scala> implicit val precision = Precision(0.001)
precision: Precision = Precision(0.001)
scala> 0.0 ~= 0.00001
res3: Boolean = true
And now it works cleanly due to Scala’s powerful features.
In this article, we’ve learned the problems of comparing floating point numbers and possible solutions. We detailed how to leverage the Scala language’s powerful features, such as implicit conversions, to create an easy-to-use comparison method.