Tidy up bigint multiplication methods#132195
Merged
bors merged 1 commit intorust-lang:masterfrom Dec 31, 2024
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This tidies up the library version of the bigint multiplication methods after the addition of the intrinsics in #133663. It follows this summary of what's desired for these methods.
Note that, if
2H = N, thenuH::MAX * uH::MAX + uH::MAX + uH::MAXisuN::MAX, and that we can effectively add two "carry" values without overflowing.For ease of terminology, the "low-order" or "least significant" or "wrapping" half of multiplication will be called the low part, and the "high-order" or "most significant" or "overflowing" half of multiplication will be called the high part. In all cases, the return convention is
(low, high)and left unchanged by this PR, to be litigated later.API Changes
The original API:
The added API:
Additionally, a naive implementation has been added for
u128andi128since there are no double-wide types for those. Eventually, an intrinsic will be added to make these more efficient, but rather than doing this all at once, the library changes are added first.Justifications for API
The unsigned parts are done to ensure consistency with overflowing addition: for a two's complement integer, you want to have unsigned overflow semantics for all parts of the integer except the highest one. This is because overflow for unsigned integers happens on the highest bit (from
MAXto zero), whereas overflow for signed integers happens on the second highest bit (fromMAXtoMIN). Since the sign information only matters in the highest part, we use unsigned overflow for everything but that part.There is still discussion on the merits of signed bigint addition methods, since getting the behaviour right is very subtle, but at least for signed bigint multiplication, the sign of the operands does make a difference. So, it feels appropriate that at least until we've nailed down the final API, there should be an option to do signed versions of these methods.
Additionally, while it's unclear whether we need all three versions of bigint multiplication (widening, carrying-1, and carrying-2), since it's possible to have up to two carries without overflow, there should at least be a method to allow that. We could potentially only offer the carry-2 method and expect that adding zero carries afterword will optimise correctly, but again, this can be litigated before stabilisation.
Note on documentation
While a lot of care was put into the documentation for the
widening_mulandcarrying_mulmethods on unsigned integers, I have not taken this same care forcarrying_mul_addor the signed versions. While I have updated the doc tests to be more appropriate, there will likely be many documentation changes done before stabilisation.Note on tests
Alongside this change, I've added several tests to ensure that these methods work as expected. These are alongside the codegen tests for the intrinsics.