Conversation
operator== compares x, y, width and height with ofIsFloatEqual (epsilon tolerance), but operator!= compared the same fields with the raw != operator instead of negating operator==. For a pair of rectangles whose fields differ by less than the epsilon tolerance (for example by a single float ULP), operator== returned true while operator!= also returned true for the same pair, violating the basic invariant that the two operators must be logical complements. An older closed issue about ofVec3f/ofVec4f operator==/operator!= relying on raw float comparisons shows how this happened: a 2016 pull request switched ofRectangle's operator== to ofIsFloatEqual for the same reason, and the equivalent fix was later judged not worth making in ofVec3f/ofVec4f because those types were being deprecated, but operator!= in ofRectangle was never updated to match operator==, leaving it comparing the raw fields. Fix operator!= to simply negate operator==, and add a regression test exercising both the ULP case and a clearly different pair of rectangles.
|
You are correct the floating point math compare is a problem in this case. Interesting using the ptr path, I'll analyse the code and run some further tests |
|
I verified PR #8567: the fix is correct. On master, == and != give contradictory answers in about 166k of 4M comparisons; with the PR they never do. Tested: I compiled the real ofRectangle.cpp from master and from the PR against the actual oF headers. I stubbed only the logging and ofMap symbols it links against. Then I ran the same harness on both builds, checking two things for every pair:
What I covered:
Behaviour changes to know about. These all come from the existing ofIsFloatEqual that == uses; the PR doesn't introduce them, but != now inherits them:
|
|
I think this is good to go, tests really verified it and historical fix to fix the same |

operator== compares x/y/width/height with ofIsFloatEqual (epsilon tolerant), but operator!= compared the same fields with raw !=. For two rectangles whose fields differ by less than the epsilon (e.g. a width of 1.0f vs std::nextafter(1.0f, 2.0f), a one ULP difference), both
a == banda != bevaluate true for the same pair. Anything that relies on == and != being complements (containers, dedup logic, assertions) gets a contradictory answer for ofRectangle objects built from accumulated floating point math, which is a routine case (scaling, translation, layout).An older closed issue about ofVec3f/ofVec4f operator==/operator!= relying on raw float comparisons shows how this happened: a 2016 pull request switched operator== for ofRectangle to ofIsFloatEqual for the same reason, and the maintainer's comment closing that issue says the equivalent fix was judged not worth making in ofVec3f/ofVec4f because those types were being deprecated, but that "it has been fixed in ofRectangle and other core classes." operator!= was never updated to match operator==, so it kept comparing the raw fields.
operator!= now returns
!(*this == rect)in libs/openFrameworks/types/ofRectangle.cpp, so the two operators can't disagree by construction.Added tests/types/ofRectangleTests/, modeled on the existing tests/types/parameters project, using the ofxUnitTests addon convention used elsewhere under tests/. It checks:
Tested two ways. First, a standalone compile of the real ofRectangle.cpp in isolation (no engine build, no mocks) through a before/fix/revert cycle on the ULP pair: before the fix the binary printed
a == b : true/a != b : true, after the fixa == b : true/a != b : false, and reverting the source reproduced the original output. Second, the exact six assertions in tests/types/ofRectangleTests/src/main.cpp were run against the same isolated build of ofRectangle.cpp: all six pass against the fixed source, and the ULP-case assertion ("operator!= must be the logical negation of operator==") is the only one that fails against the original source, with the other five passing in both cases. The new test project itself was not run through the full openFrameworks engine build locally; it follows the structure of the CI-validated tests/types/parameters project and will be built and run by the project's own CI, which builds and runs every directory under tests/.Platform tested: Linux (isolated unit compile of ofRectangle.cpp with g++, not the full OF build).