-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Approx(0.0) does not approx anything #1444
Comments
This is a fundamental problem with 0, in that, a relative comparison with sane (< 1) epsilon will always fail when the target is 0, even if it takes into account either of the sides. Consider expression By setting |
I think I do understand the math and the fundamental problem. I still think that a better solution can be found, not in terms of mathematical "correctness", but in terms of matching what the user expects, which is that Approx(0.0) does actually approximate something. I am aware that this has probably discussed to death. I hope to add another point of view in the following, if I'm just repeating what has already been said then I'm sorry. I would suggest to either add a special case for zero, using some reasonably guesstimated fixed epsilon in that case, simply to create a practical solution for an annoyance in a common case (comparing to zero). A more elaborate rationale would be that when comparing to Alternatively I would suggest to add a minimum epsilon to the calculated one to not let the applied epsilon become ridiculously small for small numbers and ultimately zero, or in other words, to use |
The problem with setting a minimum ε is that it also leads to unintuitive, and what is worse, incorrect, results. Let's say that we set minimum ε to Also, |
Alright, I agree that the minimum epsilon is a very inconsistent solution. Any practical downside for the special-case-for-zero-solution? I can't imagine people who know what they are doing to expect Approx(0.0) to not approximate anything or to be surprised if it did approximate something. |
@karyon Would you be happy with using ULP matchers instead? I think that is the better way to achieve what you want. |
Thanks. However, I can't explain my colleagues that they can use Approx if they want, but for zeroes, that does not work and they should use something else instead. We preferred practicality and modified Approx instead (we set the scale to std::numeric_limits::epsilon by default i think) |
A effective comparison between doubles should compare the exponent bits and fraction bits separately, 0.0 can be well handled in this case. |
@Danielhu229 writes:
Not around zero. The problem isn't the zero point, it's the fact that floating-point numbers near zero (over 8 million values) are denormalized. For these values, the exponent and mantissa are mixed together. Particularly around zero, there's a danger of advertising one approach as the right one. In this particular case, where you're testing around a fixed value, an epsilon value can make sense (though not a thoughtlessly chosen one). An ULP comparison ends up being equivalent, again, due to the fixed comparison point. However, there's a huge relative leap from the smallest positive value to zero to the largest negative value. In addition, a sign-change here could mean a clear error that should be flagged. For all these reasons, I'd prefer the following example code, as it's much simpler, vastly more clear, and does not depend on the reader having memorized the underlying current implementation of Catch2's
|
Just encountered this issue as well. I understand the floating point issue, however I think it is a bug in the sense that asking the API doing something does nothing. |
I just stumbled on this problem and lost some time figuring out why the approximation didn't work. I let you decide what's the best solution for this but in the meantime, can this be at least explicitly documented ? This would probably help people to not fall into this trap |
Previously, Approx::m_scale was set 1.0 by default and there were no problems with Approx(0.0). By the recent version, Approx::m_scale default value has been changed to 0.0. Does anybody know, why? |
I think that as @jayeshbadwaik commented above, ULP comparison is the way to go. |
I can only very highly endorse @BenjaminNavarro remark of at least documenting this. But in the end I also think that the API should allow for REQUIRE(a == Approx(0.0)) to work... |
@tdegeus I agree, it seems broken that it does not work at all. Even if it worked by requiring the user to set a default epsilon for |
Many developers have spent too much time on this for many moons now. Add A more subtle approach would be to have |
As someone who is looking into using this library, I wanted to share my thoughts. I think the present solution where Other than this, an issue I'm glad I found on google when looking up Approx (as it's not mentioned in the documentation), the library is looking great and I think I will use it. |
For future readers -- Martin explains the logic behind the Catch2 implementation here very well: https://codingnest.com/the-little-things-comparing-floating-point-numbers/ |
I guess we all agree, as the linked blog post explains, that floating point comparison is difficult and that's why use The but core issue remains, The doc still doesn't cover this issue so we can expect people falling into the same trap. And the code doesn't check for this particular case to warn the users. But looking at the other member functions, |
Fully agree with you, @BenjaminNavarro. As Martin writes in the piece I linked:
That is all fine and dandy (a valid design/maintenance decision), but then why not mark And on top of that, specifically for the NOTE: If I'm not mistaken, a user-changed non-zero margin does allow us to compare with 0.0 ( |
This is correct. The constructor cannot guard against 0, because The reason is that if it is on-by-default, there has to be a way to suppress it, so it requires adding a new CLI flag as well. It also If it is off-by-default, then it is mostly useless, as people are unlikely to get bitten by this repeatedly, which is the main reason to have a warning flag for something. About docs, the current Approx docs haven't been updated in ages. The two tell-tale signs are that it still uses "Catch" as the framework name, and that the markdown sources do not adhere to a column limit, unlike docs written in the last 3+ years. I am going to rewrite the docs when I find extra time, including some introduction into floating point comparisons, but that does run into the perennial problem; I have to find free time. |
Similar to #1079, #1096, and #1174.
Right now,
REQUIRE(value == Approx(0.0))
will fail unless value is exactly zero. That is,Approx(0.0)
has no effect andvalue == Approx(0.0)
behaves the same asvalue == 0.0
(not sure about -0.0 though). The reason is that with the current implementation, the epsilon used for comparison is multiplied with zero and thus has no effect.That is unexpected. I understand the reason why this happens, and I understand that float comparisons are hard. However, this makes using catch harder to use with no advantage obvious to the user, and I do think there has to be a better way, even if it's just a special case for zero.
In any case, I would suggest to at least document that you cannot use
Approx
on zero, even though I would much prefer if the practicality and user experience would take precedence over the technical purity of the current solution.The text was updated successfully, but these errors were encountered: