-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Tidy up bigint multiplication implementations #132195
base: master
Are you sure you want to change the base?
Conversation
@rustbot author Currently drafted since tests are failing and I need to fix them, but I wanted to at least save the stuff I typed up for the description. |
This comment has been minimized.
This comment has been minimized.
Let me know when it's ready and I'll take a look. Unless you'd like feedback beforehand, of course. |
☔ The latest upstream changes (presumably #132191) made this pull request unmergeable. Please resolve the merge conflicts. |
83ea534
to
070591e
Compare
Feel free to review the parts of the code that do work, and the API specifically as mentioned in the description. I just know that my naïve implementation for |
This comment has been minimized.
This comment has been minimized.
070591e
to
218de99
Compare
This comment has been minimized.
This comment has been minimized.
218de99
to
0f11119
Compare
This comment has been minimized.
This comment has been minimized.
0f11119
to
35e3b3c
Compare
This comment has been minimized.
This comment has been minimized.
35e3b3c
to
8960e06
Compare
Oh cool, finally got it to work. Glad I added some tests with @rustbot review |
8960e06
to
1a90982
Compare
($left:expr, $right:expr$(, $($arg:tt)+)?) => { | ||
{ | ||
fn runtime() { | ||
assert_eq!($left, $right, $($arg)*); | ||
assert_eq!($left, $right, $($($arg)*),*); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just wrong before but unused, so, it never triggered any errors.
($left:expr, $right:expr) => { | ||
assert_eq_const_safe!($left, $right, concat!(stringify!($left), " == ", stringify!($right))); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helped with debugging, so, I kept it.
☔ The latest upstream changes (presumably #132238) made this pull request unmergeable. Please resolve the merge conflicts. |
1a90982
to
5b825d2
Compare
☔ The latest upstream changes (presumably #132458) made this pull request unmergeable. Please resolve the merge conflicts. |
This tidies up the library version of the bigint multiplication methods, in preparation for eventually adding intrinsic versions. It follows this summary of what's desired for these methods.
Note that, if
2H = N
, thenuH::MAX * uH::MAX + uH::MAX + uH::MAX
isuN::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
u128
andi128
since 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
MAX
to zero), whereas overflow for signed integers happens on the second highest bit (fromMAX
toMIN
). 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.
Justifications for separated
_impl
methodsRight now, we know that the performance of these methods is less than ideal, since bigint multiplication is definitely an area where having an intrinsic will improve optimisation. Because of the requirement for a distinct implementation for
u128
, I decided to simply have these separate methods containing the actual implementation and leave the documentation and doc tests inside the macros used to define the methods for all types, so that the documentation between the different versions cannot drift. Once an intrinsic version exists on the bootstrap compiler, the "impl" calls will be replaced with intrinsic calls in all cases.Note on documentation
While a lot of care was put into the documentation for the
widening_mul
andcarrying_mul
methods on unsigned integers, I have not taken this same care forcarrying2_mul
or 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. This should help bolster confidence that the naive implementations are actually correct.
Note on naming
I know that
carrying2_mul
feels like an awful name, and it likely won't be the final name for this function. The goal here is to offer this function in some form in the library so that the implementation is there and accepted, even though the name of the function will likely change before stabilisation. Remember that all of this is still unstable and these will all be discussed before stabilisation.