Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d117b7f

Browse files
committedDec 31, 2024
Auto merge of #132195 - clarfonthey:bigint-mul, r=scottmcm
Tidy up bigint multiplication methods This tidies up the library version of the bigint multiplication methods after the addition of the intrinsics in #133663. It follows [this summary](#85532 (comment)) of what's desired for these methods. Note that, if `2H = N`, then `uH::MAX * uH::MAX + uH::MAX + uH::MAX` is `uN::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: ```rust impl uN { // computes self * rhs pub const fn widening_mul(self, rhs: uN) -> (uN, uN); // computes self * rhs + carry pub const fn carrying_mul(self, rhs: uN, carry: uN) -> (uN, uN); } ``` The added API: ```rust impl uN { // computes self * rhs + carry1 + carry2 pub const fn carrying2_mul(self, rhs: uN, carry: uN, add: uN) -> (uN, uN); } impl iN { // note that the low part is unsigned pub const fn widening_mul(self, rhs: iN) -> (uN, iN); pub const fn carrying_mul(self, rhs: iN, carry: iN) -> (uN, iN); pub const fn carrying_mul_add(self, rhs: iN, carry: iN, add: iN) -> (uN, iN); } ``` Additionally, a naive implementation has been added for `u128` and `i128` 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 (from `MAX` to `MIN`). 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_mul` and `carrying_mul` methods on unsigned integers, I have not taken this same care for `carrying_mul_add` 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. These are alongside the codegen tests for the intrinsics.
2 parents 34719e8 + f228458 commit d117b7f

File tree

11 files changed

+388
-123
lines changed

11 files changed

+388
-123
lines changed
 

‎library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
#![cfg_attr(bootstrap, feature(do_not_recommend))]
111111
#![feature(array_ptr_get)]
112112
#![feature(asm_experimental_arch)]
113+
#![feature(bigint_helper_methods)]
113114
#![feature(const_carrying_mul_add)]
114115
#![feature(const_eval_select)]
115116
#![feature(core_intrinsics)]

‎library/core/src/num/int_macros.rs

+108
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,114 @@ macro_rules! int_impl {
25122512
(a as Self, b)
25132513
}
25142514

2515+
/// Calculates the complete product `self * rhs` without the possibility to overflow.
2516+
///
2517+
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
2518+
/// of the result as two separate values, in that order.
2519+
///
2520+
/// If you also need to add a carry to the wide result, then you want
2521+
/// [`Self::carrying_mul`] instead.
2522+
///
2523+
/// # Examples
2524+
///
2525+
/// Basic usage:
2526+
///
2527+
/// Please note that this example is shared between integer types.
2528+
/// Which explains why `i32` is used here.
2529+
///
2530+
/// ```
2531+
/// #![feature(bigint_helper_methods)]
2532+
/// assert_eq!(5i32.widening_mul(-2), (4294967286, -1));
2533+
/// assert_eq!(1_000_000_000i32.widening_mul(-10), (2884901888, -3));
2534+
/// ```
2535+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
2536+
#[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")]
2537+
#[must_use = "this returns the result of the operation, \
2538+
without modifying the original"]
2539+
#[inline]
2540+
pub const fn widening_mul(self, rhs: Self) -> ($UnsignedT, Self) {
2541+
Self::carrying_mul_add(self, rhs, 0, 0)
2542+
}
2543+
2544+
/// Calculates the "full multiplication" `self * rhs + carry`
2545+
/// without the possibility to overflow.
2546+
///
2547+
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
2548+
/// of the result as two separate values, in that order.
2549+
///
2550+
/// Performs "long multiplication" which takes in an extra amount to add, and may return an
2551+
/// additional amount of overflow. This allows for chaining together multiple
2552+
/// multiplications to create "big integers" which represent larger values.
2553+
///
2554+
/// If you don't need the `carry`, then you can use [`Self::widening_mul`] instead.
2555+
///
2556+
/// # Examples
2557+
///
2558+
/// Basic usage:
2559+
///
2560+
/// Please note that this example is shared between integer types.
2561+
/// Which explains why `i32` is used here.
2562+
///
2563+
/// ```
2564+
/// #![feature(bigint_helper_methods)]
2565+
/// assert_eq!(5i32.carrying_mul(-2, 0), (4294967286, -1));
2566+
/// assert_eq!(5i32.carrying_mul(-2, 10), (0, 0));
2567+
/// assert_eq!(1_000_000_000i32.carrying_mul(-10, 0), (2884901888, -3));
2568+
/// assert_eq!(1_000_000_000i32.carrying_mul(-10, 10), (2884901898, -3));
2569+
#[doc = concat!("assert_eq!(",
2570+
stringify!($SelfT), "::MAX.carrying_mul(", stringify!($SelfT), "::MAX, ", stringify!($SelfT), "::MAX), ",
2571+
"(", stringify!($SelfT), "::MAX.unsigned_abs() + 1, ", stringify!($SelfT), "::MAX / 2));"
2572+
)]
2573+
/// ```
2574+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
2575+
#[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")]
2576+
#[must_use = "this returns the result of the operation, \
2577+
without modifying the original"]
2578+
#[inline]
2579+
pub const fn carrying_mul(self, rhs: Self, carry: Self) -> ($UnsignedT, Self) {
2580+
Self::carrying_mul_add(self, rhs, carry, 0)
2581+
}
2582+
2583+
/// Calculates the "full multiplication" `self * rhs + carry1 + carry2`
2584+
/// without the possibility to overflow.
2585+
///
2586+
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
2587+
/// of the result as two separate values, in that order.
2588+
///
2589+
/// Performs "long multiplication" which takes in an extra amount to add, and may return an
2590+
/// additional amount of overflow. This allows for chaining together multiple
2591+
/// multiplications to create "big integers" which represent larger values.
2592+
///
2593+
/// If you don't need either `carry`, then you can use [`Self::widening_mul`] instead,
2594+
/// and if you only need one `carry`, then you can use [`Self::carrying_mul`] instead.
2595+
///
2596+
/// # Examples
2597+
///
2598+
/// Basic usage:
2599+
///
2600+
/// Please note that this example is shared between integer types.
2601+
/// Which explains why `i32` is used here.
2602+
///
2603+
/// ```
2604+
/// #![feature(bigint_helper_methods)]
2605+
/// assert_eq!(5i32.carrying_mul_add(-2, 0, 0), (4294967286, -1));
2606+
/// assert_eq!(5i32.carrying_mul_add(-2, 10, 10), (10, 0));
2607+
/// assert_eq!(1_000_000_000i32.carrying_mul_add(-10, 0, 0), (2884901888, -3));
2608+
/// assert_eq!(1_000_000_000i32.carrying_mul_add(-10, 10, 10), (2884901908, -3));
2609+
#[doc = concat!("assert_eq!(",
2610+
stringify!($SelfT), "::MAX.carrying_mul_add(", stringify!($SelfT), "::MAX, ", stringify!($SelfT), "::MAX, ", stringify!($SelfT), "::MAX), ",
2611+
"(", stringify!($UnsignedT), "::MAX, ", stringify!($SelfT), "::MAX / 2));"
2612+
)]
2613+
/// ```
2614+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
2615+
#[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")]
2616+
#[must_use = "this returns the result of the operation, \
2617+
without modifying the original"]
2618+
#[inline]
2619+
pub const fn carrying_mul_add(self, rhs: Self, carry: Self, add: Self) -> ($UnsignedT, Self) {
2620+
intrinsics::carrying_mul_add(self, rhs, carry, add)
2621+
}
2622+
25152623
/// Calculates the divisor when `self` is divided by `rhs`.
25162624
///
25172625
/// Returns a tuple of the divisor along with a boolean indicating whether an arithmetic overflow would
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.