Search code examples
scalananequalityscalatestscalactic

ScalaTest Scalactic - Custom Double Equality with tolerance including Double.NaN case


I am trying to create a custom matcher that will take into account Double.NaN and will use tolerance for non-nan values.

import org.scalactic.{Equality, TolerantNumerics}
import org.scalatest.Matchers

trait MoreMatchers extends Matchers {

  implicit def doubleEqWithNaNAndTol: Equality[Double] = new Equality[Double] {

    implicit val tolerance: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1e-6)

    def areEqual(expected: Double, actual: Any): Boolean = actual match {
      case number: Double if number.isNaN => expected.isNaN
      case _: Double => actual === expected
      case _ => false
    }

  }

Unfortunately, it doesn't work.

assert(0.00226685508536916 === 0.0022668550853691587)  // failure - doesn't use tolerance
assert (Double.NaN === Double.NaN )  // success

If enter the tolerance within the assertion then it fails if there are NaN.

assert(0.00226685508536916 === 0.0022668550853691587 +- 1e-6)  // success
assert (Double.NaN === Double.NaN  +- 1e-6)  // failure - can't use tolerance when NaN

If I just call it like the following, then it works.

implicit val tolerance: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1e-6)

def eq(expected: Double, actual: Double): Boolean = expected match {
    case x if x.isNaN => actual.isNaN
    case _ => actual === expected
}

And then call it as:

assert(eq(...,...))

I am wondering if it is possible to get it working using the first way. Have you come across such case before? Could you suggest any solutions? Any help will be appreciated :)

Thanks, ele


Solution

  • Many thanks to eirikr @d6 :), see solution in custom equality gist

    Basically in the above code you need to use tolerance.areEqual(expected, number) when comparing non-nan doubles in order to be able to use the tolerance implicit in that comparison.

    import org.scalactic.{Equality, TolerantNumerics}
    import org.scalatest.Matchers
    
    trait MoreMatchers extends Matchers {
    
      implicit def doubleEqWithNaNAndTol: Equality[Double] = new Equality[Double] {
    
        implicit val tolerance: Equality[Double] = TolerantNumerics.tolerantDoubleEquality(1e-6)
    
        def areEqual(expected: Double, actual: Any): Boolean = actual match {
          case x: Double if x.isNaN => expected.isNaN
          case x: Double => tolerance.areEqual(expected, x)
          case _ => false
        }
    
      }
    

    Regards, ele