Clipper2: Unexpected collinearity test failure on M1 macOS
I’m experiencing something that looks like unexpected collinearity check failures (tested on main
as well as latest release).
When unioning the two triangles below, the result is two triangles, but should be a single triangle as the vertices on the X axis are clearly collinear. I’m also not using the full 64-bit range (just approx 31 bits), so it feels like this should be well within the operating range of Clipper.
A failing C++ test case:
#include <gtest/gtest.h>
#include "clipper2/clipper.h"
using namespace Clipper2Lib;
TEST(Clipper2Tests, TestUnionIssue) {
Paths64 subject;
subject.push_back(MakePath({0, -453054451,0, -433253797,-455550000, 0}));
subject.push_back(MakePath({0, -433253797,0, 0,-455550000, 0}));
Clipper64 clipper;
clipper.PreserveCollinear(false);
clipper.AddSubject(subject);
Paths64 solution;
clipper.Execute(ClipType::Union, FillRule::NonZero, solution);
ASSERT_EQ(solution.size(), 1);
EXPECT_EQ(solution[0].size(), 3);
EXPECT_EQ(IsPositive(subject[0]), IsPositive(solution[0]));
}
Note: This is a minimal failing example. What I’m actually trying to achieve is to project a 3D cone onto one axis and unioning all the individual triangles until I get a single triangular silhouette.
About this issue
- Original URL
- State: closed
- Created 5 months ago
- Comments: 39 (15 by maintainers)
Commits related to this issue
- Clipper.Engine: fixed collinearity test error in macOS (#777) — committed to AngusJohnson/Clipper2 by AngusJohnson 3 months ago
- Added Collinearity test // #777 — committed to AngusJohnson/Clipper2 by AngusJohnson 3 months ago
- another attempt to fix collinearity testing on MacOS (C++) (#777) — committed to AngusJohnson/Clipper2 by AngusJohnson 2 months ago
- Improved IsCollinear function (#777) — committed to AngusJohnson/Clipper2 by AngusJohnson 2 months ago
Just my 5 cents, maybe this can help you somehow, because I have some experience in working with M1 macs, here are some potential differencies that can cause troubles:
(Sidenote: std::sqrt is producing perfectly equal results on all sane platforms)
In your place I would first try to find trigonometric function that introduces difference, but I am not sure if clipper uses any for simple clipping (I believe it uses sin and cos for offsetting, but we are not talking about offsetting). Second option is to try adding -ffp-contract=off and checking if issue goes away. If it does - some multiply-add operation is producing slightly different result and this reveals a bug.
ADDED: Forgot to mention, most of the time all issues except first two are reproducible on any CPU and OS with Clang + libc++ build. You can try reproducing it like this as well, maybe it will work.
As for C++, I have updated the PR to use unsigned integers, for which the result should be well-defined: the values should just wrap (regardless of compiler settings it seems), which I believe is exactly what we want. I have also added a simple test case (which would fail for the current version that uses
double
).Yeah, that’s a good point. I think at minimum, there should be a test case that gets executed as part of the GitHub Actions tests. I could try and add one for C++?
No I didn’t. Sorry. And looking at this problem again I can now see a very simple solution.
I was wondering why this was not defaulting to =off and found this: https://releases.llvm.org/14.0.0/tools/clang/docs/ReleaseNotes.html#floating-point-support-in-clang
So it is fnmsub instruction in M1 build that is causing a difference, and it is the CrossProduct function that ends up having this opcode. I added some debug info to it and started the test:
And here is the difference between M1 build (on the left side) and AMD64 build (on the right) https://www.diffchecker.com/kaHSyAnl/
I guess to reproduce this problem on a normal AMD64 Angus can try to use those results from the left side of the diff.