I think the issue here even goes back to people (myself certainly included) that weren’t mentally thinking about what a \approx b means properly. Undergraduates and RAs who are often writing test cases are also unlikely to think through the nuances of relative comparisons and will probably just pull a tolerance out of thin air. On benefit of the current \approx is that is does the relative tolerance calculation for people properly.
From the model of this, it seems that it is,
a \approx b\quad \text{iff} \quad \frac{a}{b}\approx 1 \approx \frac{b}{a}
If this is indeed true, then the real issue is not even floating point math, but rather that this is undefined if either a=0 and b=0. We wouldn’t have a division by 0 silently ignored, so why would we here?
As for redefining this as
a \approx b\quad \text{iff} \quad \frac{a+ \mathbf{1}(a = 0 || b = 0)}{b + \mathbf{1}(a = 0 || b = 0)} \approx 1 there is an ugly discontinuity in whether the operation is true as a \to 0 or b\to 0.
More importantly, I am betting that 90% of the uses of a ≈ 0
comes to two cases (this is a dogmatic prior, no evidence!):
- People get in the habit of checking scalars
a - b ≈ 0
because they used to do abs(a - b) < tol
. Here they could just have easily done a ≈ b
with a small code rearrangement, which might even be cleaner in notation.
- Testing whether two vectors are similar by checking ||A - B||_K \approx 0 for some K norm, and calling it with
norm(A-B,Inf)≈0
or something like that. But the fallback for abstract arrays with isapprox
basically does the ∞ norm. So in that case I think the warning should also be to call it with a ≈ b
for vectors if the infinity norm is good enough. It will broadcast to the correct scalar version.
Here is a starting point for the isapprox
that could be useful (where the array version doesn’t need to change)
function isapprox(x::Number, y::Number; rtol::Real=rtoldefault(x,y), atol::Real=0, nans::Bool=false)
if(x != y && (x==zero(Number) || y==zero(Number)))
warn("isapprox(x,y) cannot be called with x=0 or y=0. If you are using isapprox(x - y, 0) or isapprox(norm(x-y),0) the rearrange code to be isapprox(x,y) for either scalars or vectors")
throw(DomainError())
else
return x == y || (isfinite(x) && isfinite(y) && abs(x-y) <= atol + rtol*max(abs(x), abs(y))) || (nans && isnan(x) && isnan(y))
end
end
Try it with
isapprox(1.0,1.000000001)
isapprox(1.0 - 1.000000001,0)
isapprox([1.0;0.1], [1.00000001;0.10000001])