Numerical convergence and stability

Testing floating point values for equality rarely makes sense, regardless of how standardized operations are for reproducibility, because floating point values are inherently approximations, which means formulas that are supposed to be mathematically equivalent may not give the same result.

For example, you cannot assume that things like:

a+b+c == c+b+a

a*(b+c) == ab + ac

(also, keep in mind Double.NaN == Double.NaN is false)

The standard solution is to specify the relative error you are willing to tolerate. Double precision is about 1e-16, but rounding errors propagate and accumulate. Different calculations vary in stability, but a rule of thumb is that the number of digits lost is about the base ten logarithm of the number of operations.

def relativeError(x: Double, y: Double): Double =
Math.abs(x-y)/(0.5*(Math.abs(x) + Math.abs(y)))

val epsilon = 1e-12

def areCloseEnough(x: Double, y: Double): Boolean =

(x == y) || ( relativeError(x, y) < epsilon)