I'm quite a strong -1 on this proposal.
TL;DR I think this proposal adds a function that overstates its correctness and will lead to further confusion. It's consciously imperfect, and such functions should have a very high bar of entry into the standard library.
As the proposal admits, floating point comparisons are difficult and prone to error. I disagree with the proposal’s core idea that "we can define approximate equality functions that are better than what most people will come up with without assistance from the standard library", rather the final solution is "the best we can do with floating point numbers that can vary massively in scale without any additional context from the caller". It turns a non-simple problem into something deceptively simple. Providing it as a standard library function implies correctness and may deter users from understanding its quirks under the assumption they're using correct functions.
I find the handling of 0 a particular concern. The big selling point of this function is that it'll do something reasonable given any two floating point numbers. The caveat being if either operand is 0 it won't. If we've already conceded that we don't know the scale of the operands to this function, there's no reason to expect them to be non-zero. One solution proposed in this thread is to require the programmer to know their inputs and fall back to an absolute tolerance if they know they can be zero. We're now back in the realm of "rolling your own", which is what this proposal seeks to avoid. Python's similar function math.isclose
provides an absolute tolerance for near-zero comparisons which, if nothing else, documents this quirk.
Perhaps I'm not in the field where this function really shines. The only time I've wanted to compare floating point numbers for equality is when writing unit tests. Many unit test frameworks will provide functions for this express purpose, often allowing you to choose between relative, absolute, and units in the last place (ULPs). In most cases, unit tests will be comparing against a known, constant, expected value and so a scale-agnostic comparison is not necessary. In my opinion such functions belong quite happily in a unit test framework.
Further, the times I can see this function being useful outside unit testing is slim. Or, at least, times it's better than an alternative are few:
- If you know the scale and accuracy of your inputs, use an appropriate absolute tolerance that takes into account that as well as accuracy lost in calculation.
- If you're displaying to a user, use a rounding factor that they'd expect (e.g. to one decimal place) and compare
- Prefer using inequalities (
>
, <
) where appropriate.
To me the following snippet is a demonstration of code that works as expected, but would be confusing to new programers once they're told x.isAlmostEqual
is the correct function to use:
[(10.0, 10.1), (1.0, 1.1), (0.0, 0.000001)].map {
(x, y) in x.isAlmostEqual(to: y, tolerance: 0.01) // [true, false, false]
}
- 10.0 is almost equal to 10.1. Exactly what I wanted!
- 1.0 is not almost equal to 1.1; that's odd, it's the same difference as 10.0 to 10.1
- 0 is not almost equal to 0.000001; that's really odd. They're basically exactly the same!
I think it's fair to be confused in this case, just as it's fair to be confused when apparently equal floating point numbers aren't equal. isAlmostEqual
is not the function they were looking for. Instead, they wanted a function that compared within an absolute tolerance, something this proposal singles out as being "wrong more often than it is right". We've just given them yet another function to learn the appropriate use of, and delayed the introduction of how floating point numbers are represented.
Again, perhaps I'm not the indented audience. I certainly feel like I'm in the minority. Could people who are positive about this provide real world examples when they're trying to compare two floating point numbers where this would be better than an alternative above?
It's a real problem, but not one I consider the be solvable with such a simple API.
In spirt, yes. In implementation, no.
I've used similar functions in unit testing frameworks. I've seen that Python includes math.isclose
but never used it.
A fair amount. I've read the proposal and this thread. I've tried the prototype implementation.