1. Overview

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.

2. Floating Point Numbers

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.

3. Comparing Floating Point Numbers

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:

  1. Avoiding using floating point numbers if precision is strictly essential (in financial services, for instance). Scaling the units is a very common solution. Instead of representing all measurements in meters, we can represent them in millimeters, making all values integers.
  2. Using a specific high-precision library/class for that. Java has the BigDecimal class, which can also be used in Scala. Unfortunately it makes our code more verbose, and it may not be a performant solution if we’re writing specific low-latency applications.
  3. Implementing custom comparison with a given precision error.

3.1. Comparisons With Precision Error

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

3.2. Custom Comparison Operator

We can make the comparison even cleaner by using Scala implicit classes and defining a new operator:

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

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

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.

4. Conclusion

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.

Comments are closed on this article!