Fix bugs in fixed division#5698
Conversation
|
I believe I have found a way to implement unsigned to signed conversion with wraparound in C++ without relying on implementation defined behavour. Would this be overkill? #include <cstdint>
typedef int64_t Sint64;
typedef uint64_t Uint64;
Sint64 good(Uint64 num) {
if (num > (Uint64) INT64_MAX) {
// Implement wrapardound
num -= (Uint64) INT64_MAX;
num -= 1;
Sint64 result = num;
result += INT64_MIN;
return result;
}
return num;
} |
|
@hexagonrecursion Since you seem to have been playing around with our code base, I'm just informing you we'll soon feature freeze (-ish), for the annual February 03 release. |
|
Thanks. I have a lot on my plate though so I don't expect to make more pioneer pull requests any time soon. |
The primary concern of the fixed-point function library is determinism between GCC-on-Linux and MSVC-on-Windows. Performance is a very close second, so if GCC and MSVC provide uniform outcomes of their implementation-defined behavior (and Clang can be tested to comply with the expected behavior) then I would strongly advise against introducing additional branches into math code that is expected to have extremely high throughput.
EDIT: I had not yet reached the point in the diff where a test case doing exactly that had been added, disregard! |
sturnclaw
left a comment
There was a problem hiding this comment.
Overall, I'm quite glad someone is addressing this. I believe the fixed-point code to either predate or be written around the time C++11 was introduced, and certainly well before Pioneer began adopting the standard, much less C++17.
Thank you for taking the time to ensure it is numerically correct and stable, I'm sure it will save some time in the long run otherwise spent hunting down ghost bugs!
I've left one change request that I'd like to see addressed before merge - minimizing branches where possible in high-throughput code is strongly preferred, and the system generation code performance as a whole is primarily dependent on the fixed-point math library.
|
@hexagonrecursion Did you have time to address the requested changes? We might do a release next month, so just checking status on this PR. |
Note: this is technically undefined behavior if a.v or b.v is _exactly_ INT64_MIN, but the upside that this compiles to faster code even under -Og
sturnclaw
left a comment
There was a problem hiding this comment.
I'd suggest a "canary" test be added to the test suite to check for the behavior of division tests involving INT64_MIN which rely on undefined behavior. Otherwise, this looks good to me - thanks for addressing the review feedback.
Because this has a non-zero chance of implicitly altering procedural generation determinism when merged, I'm going to defer merge of this PR until we've fully started the development cycle for the next major release.
@Web-eWorks I do not understand. At the time of writing none of the tests added by this pull request (to the best of my knowledge) rely on undefined behavior. Are you suggesting we add a test that deliberately triggets undefined behaviour? What would this test assert? The use of std::abs() is to the best of my knowledge the only source of undefined behaviour in my current implementation of undefined behaviour-free version: undefined behaviour version:
|
What do you think I should do?Brainstorming alternatives:
|
sturnclaw
left a comment
There was a problem hiding this comment.
I consider the current implementation acceptable, as division of or by INT64_MIN is unlikely to intentionally occur without also triggering overflow.
The primary intent of a test was to ensure that the undefined behavior of the platform in question matches the expected result of the undefined behavior, i.e. to validate that:
fixed(INT64_MIN) / fixed(1, 1) == fixed(EXPECTED_VALUE);If that test failed on a platform we intend to support, we would have a reason to replace the std::abs() implementation with the slower but fully-defined implementation, as the real-world effect would be a loss of determinism.
In short: undefined behavior isn't a bad thing as long as it's the same undefined behavior on all platforms we intend to support.
Fixes #5697
Note: the return statement at the end does convert Uint64 to Sint64, which is implementation-defined in C++17 if the value is > INT64_MAX. Here is why I think this is probably OK: