Skip to content

Commit e7a247d

Browse files
authored
Rollup merge of #85017 - clarfonthey:carrying_widening, r=m-ou-se
Add carrying_add, borrowing_sub, widening_mul, carrying_mul methods to integers This comes in part from my own attempts to make (crude) big integer implementations, and also due to the stalled discussion in [RFC 2417](rust-lang/rfcs#2417). My understanding is that changes like these are best offered directly as code and then an RFC can be opened if there needs to be more discussion before stabilisation. Since all of these methods are unstable from the start, I figured I might as well offer them now. I tried looking into intrinsics, messed around with a few different implementations, and ultimately concluded that these are "good enough" implementations for now to at least put up some code and maybe start bikeshedding on a proper API for these. For the `carrying_add` and `borrowing_sub`, I tried looking into potential architecture-specific code and realised that even using the LLVM intrinsics for `addcarry` and `subborrow` on x86 specifically, I was getting exactly the same assembly as the naive implementation using `overflowing_add` and `overflowing_sub`, although the LLVM IR did differ because of the architecture-specific code. Longer-term I think that they would be best suited to specific intrinsics as that would make optimisations easier (instructions like add-carry tend to use implicit flags, and thus can only be optimised if they're done one-after-another, and thus it would make the most sense to have compact intrinsics that can be merged together easily). For `widening_mul` and `carrying_mul`, for now at least, I simply cast to the larger type and perform arithmetic that way, since we currently have no intrinsic that would work better for 128-bit integers. In the future, I also think that some form of intrinsic would work best to cover that case, but for now at least, I think that they're "good enough" for now. The main reasoning for offering these directly to the standard library even though they're relatively niche optimisations is to help ensure that the code generated for them is optimal. Plus, these operations alone aren't enough to create big integer implementations, although they could help simplify the code required to do so and make it a bit more accessible for the average implementor. That said, I 100% understand if any or all of these methods are not desired simply because of how niche they are. Up to you. 🤷🏻
2 parents 76d18cf + cc15047 commit e7a247d

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
#![feature(const_alloc_layout)]
7777
#![feature(const_arguments_as_str)]
7878
#![feature(const_assert_type)]
79+
#![feature(const_bigint_helper_methods)]
7980
#![feature(const_caller_location)]
8081
#![feature(const_cell_into_inner)]
8182
#![feature(const_discriminant)]

library/core/src/num/int_macros.rs

+54
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,33 @@ macro_rules! int_impl {
13411341
(a as Self, b)
13421342
}
13431343

1344+
/// Calculates `self + rhs + carry` without the ability to overflow.
1345+
///
1346+
/// Performs "ternary addition" which takes in an extra bit to add, and may return an
1347+
/// additional bit of overflow. This allows for chaining together multiple additions
1348+
/// to create "big integers" which represent larger values.
1349+
///
1350+
/// # Examples
1351+
///
1352+
/// Basic usage
1353+
///
1354+
/// ```
1355+
/// #![feature(bigint_helper_methods)]
1356+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".carrying_add(2, false), (7, false));")]
1357+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".carrying_add(2, true), (8, false));")]
1358+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.carrying_add(1, false), (", stringify!($SelfT), "::MIN, false));")]
1359+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.carrying_add(1, true), (", stringify!($SelfT), "::MIN + 1, false));")]
1360+
/// ```
1361+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
1362+
#[rustc_const_unstable(feature = "const_bigint_helper_methods", issue = "85532")]
1363+
#[must_use = "this returns the result of the operation, \
1364+
without modifying the original"]
1365+
#[inline]
1366+
pub const fn carrying_add(self, rhs: Self, carry: bool) -> (Self, bool) {
1367+
let (sum, carry) = (self as $UnsignedT).carrying_add(rhs as $UnsignedT, carry);
1368+
(sum as $SelfT, carry)
1369+
}
1370+
13441371
/// Calculates `self` - `rhs`
13451372
///
13461373
/// Returns a tuple of the subtraction along with a boolean indicating whether an arithmetic overflow
@@ -1365,6 +1392,33 @@ macro_rules! int_impl {
13651392
(a as Self, b)
13661393
}
13671394

1395+
/// Calculates `self - rhs - borrow` without the ability to overflow.
1396+
///
1397+
/// Performs "ternary subtraction" which takes in an extra bit to subtract, and may return
1398+
/// an additional bit of overflow. This allows for chaining together multiple subtractions
1399+
/// to create "big integers" which represent larger values.
1400+
///
1401+
/// # Examples
1402+
///
1403+
/// Basic usage
1404+
///
1405+
/// ```
1406+
/// #![feature(bigint_helper_methods)]
1407+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".borrowing_sub(2, false), (3, false));")]
1408+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".borrowing_sub(2, true), (2, false));")]
1409+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN.borrowing_sub(1, false), (", stringify!($SelfT), "::MAX, false));")]
1410+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MIN.borrowing_sub(1, true), (", stringify!($SelfT), "::MAX - 1, false));")]
1411+
/// ```
1412+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
1413+
#[rustc_const_unstable(feature = "const_bigint_helper_methods", issue = "85532")]
1414+
#[must_use = "this returns the result of the operation, \
1415+
without modifying the original"]
1416+
#[inline]
1417+
pub const fn borrowing_sub(self, rhs: Self, borrow: bool) -> (Self, bool) {
1418+
let (sum, borrow) = (self as $UnsignedT).borrowing_sub(rhs as $UnsignedT, borrow);
1419+
(sum as $SelfT, borrow)
1420+
}
1421+
13681422
/// Calculates the multiplication of `self` and `rhs`.
13691423
///
13701424
/// Returns a tuple of the multiplication along with a boolean indicating whether an arithmetic overflow

library/core/src/num/mod.rs

+87
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,104 @@ depending on the target pointer size.
9393
};
9494
}
9595

96+
macro_rules! widening_impl {
97+
($SelfT:ty, $WideT:ty, $BITS:literal) => {
98+
/// Calculates the complete product `self * rhs` without the possibility to overflow.
99+
///
100+
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
101+
/// of the result as two separate values, in that order.
102+
///
103+
/// # Examples
104+
///
105+
/// Basic usage:
106+
///
107+
/// Please note that this example is shared between integer types.
108+
/// Which explains why `u32` is used here.
109+
///
110+
/// ```
111+
/// #![feature(bigint_helper_methods)]
112+
/// assert_eq!(5u32.widening_mul(2), (10, 0));
113+
/// assert_eq!(1_000_000_000u32.widening_mul(10), (1410065408, 2));
114+
/// ```
115+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
116+
#[rustc_const_unstable(feature = "const_bigint_helper_methods", issue = "85532")]
117+
#[must_use = "this returns the result of the operation, \
118+
without modifying the original"]
119+
#[inline]
120+
pub const fn widening_mul(self, rhs: Self) -> (Self, Self) {
121+
// note: longer-term this should be done via an intrinsic,
122+
// but for now we can deal without an impl for u128/i128
123+
// SAFETY: overflow will be contained within the wider types
124+
let wide = unsafe { (self as $WideT).unchecked_mul(rhs as $WideT) };
125+
(wide as $SelfT, (wide >> $BITS) as $SelfT)
126+
}
127+
128+
/// Calculates the "full multiplication" `self * rhs + carry`
129+
/// without the possibility to overflow.
130+
///
131+
/// This returns the low-order (wrapping) bits and the high-order (overflow) bits
132+
/// of the result as two separate values, in that order.
133+
///
134+
/// Performs "long multiplication" which takes in an extra amount to add, and may return an
135+
/// additional amount of overflow. This allows for chaining together multiple
136+
/// multiplications to create "big integers" which represent larger values.
137+
///
138+
/// # Examples
139+
///
140+
/// Basic usage:
141+
///
142+
/// Please note that this example is shared between integer types.
143+
/// Which explains why `u32` is used here.
144+
///
145+
/// ```
146+
/// #![feature(bigint_helper_methods)]
147+
/// assert_eq!(5u32.carrying_mul(2, 0), (10, 0));
148+
/// assert_eq!(5u32.carrying_mul(2, 10), (20, 0));
149+
/// assert_eq!(1_000_000_000u32.carrying_mul(10, 0), (1410065408, 2));
150+
/// assert_eq!(1_000_000_000u32.carrying_mul(10, 10), (1410065418, 2));
151+
/// ```
152+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
153+
#[rustc_const_unstable(feature = "bigint_helper_methods", issue = "85532")]
154+
#[must_use = "this returns the result of the operation, \
155+
without modifying the original"]
156+
#[inline]
157+
pub const fn carrying_mul(self, rhs: Self, carry: Self) -> (Self, Self) {
158+
// note: longer-term this should be done via an intrinsic,
159+
// but for now we can deal without an impl for u128/i128
160+
// SAFETY: overflow will be contained within the wider types
161+
let wide = unsafe {
162+
(self as $WideT).unchecked_mul(rhs as $WideT).unchecked_add(carry as $WideT)
163+
};
164+
(wide as $SelfT, (wide >> $BITS) as $SelfT)
165+
}
166+
};
167+
}
168+
96169
#[lang = "i8"]
97170
impl i8 {
171+
widening_impl! { i8, i16, 8 }
98172
int_impl! { i8, i8, u8, 8, 7, -128, 127, 2, "-0x7e", "0xa", "0x12", "0x12", "0x48",
99173
"[0x12]", "[0x12]", "", "" }
100174
}
101175

102176
#[lang = "i16"]
103177
impl i16 {
178+
widening_impl! { i16, i32, 16 }
104179
int_impl! { i16, i16, u16, 16, 15, -32768, 32767, 4, "-0x5ffd", "0x3a", "0x1234", "0x3412",
105180
"0x2c48", "[0x34, 0x12]", "[0x12, 0x34]", "", "" }
106181
}
107182

108183
#[lang = "i32"]
109184
impl i32 {
185+
widening_impl! { i32, i64, 32 }
110186
int_impl! { i32, i32, u32, 32, 31, -2147483648, 2147483647, 8, "0x10000b3", "0xb301",
111187
"0x12345678", "0x78563412", "0x1e6a2c48", "[0x78, 0x56, 0x34, 0x12]",
112188
"[0x12, 0x34, 0x56, 0x78]", "", "" }
113189
}
114190

115191
#[lang = "i64"]
116192
impl i64 {
193+
widening_impl! { i64, i128, 64 }
117194
int_impl! { i64, i64, u64, 64, 63, -9223372036854775808, 9223372036854775807, 12,
118195
"0xaa00000000006e1", "0x6e10aa", "0x1234567890123456", "0x5634129078563412",
119196
"0x6a2c48091e6a2c48", "[0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]",
@@ -135,6 +212,7 @@ impl i128 {
135212
#[cfg(target_pointer_width = "16")]
136213
#[lang = "isize"]
137214
impl isize {
215+
widening_impl! { isize, i32, 16 }
138216
int_impl! { isize, i16, usize, 16, 15, -32768, 32767, 4, "-0x5ffd", "0x3a", "0x1234",
139217
"0x3412", "0x2c48", "[0x34, 0x12]", "[0x12, 0x34]",
140218
usize_isize_to_xe_bytes_doc!(), usize_isize_from_xe_bytes_doc!() }
@@ -143,6 +221,7 @@ impl isize {
143221
#[cfg(target_pointer_width = "32")]
144222
#[lang = "isize"]
145223
impl isize {
224+
widening_impl! { isize, i64, 32 }
146225
int_impl! { isize, i32, usize, 32, 31, -2147483648, 2147483647, 8, "0x10000b3", "0xb301",
147226
"0x12345678", "0x78563412", "0x1e6a2c48", "[0x78, 0x56, 0x34, 0x12]",
148227
"[0x12, 0x34, 0x56, 0x78]",
@@ -152,6 +231,7 @@ impl isize {
152231
#[cfg(target_pointer_width = "64")]
153232
#[lang = "isize"]
154233
impl isize {
234+
widening_impl! { isize, i128, 64 }
155235
int_impl! { isize, i64, usize, 64, 63, -9223372036854775808, 9223372036854775807,
156236
12, "0xaa00000000006e1", "0x6e10aa", "0x1234567890123456", "0x5634129078563412",
157237
"0x6a2c48091e6a2c48", "[0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]",
@@ -164,6 +244,7 @@ const ASCII_CASE_MASK: u8 = 0b0010_0000;
164244

165245
#[lang = "u8"]
166246
impl u8 {
247+
widening_impl! { u8, u16, 8 }
167248
uint_impl! { u8, u8, 8, 255, 2, "0x82", "0xa", "0x12", "0x12", "0x48", "[0x12]",
168249
"[0x12]", "", "" }
169250

@@ -697,18 +778,21 @@ impl u8 {
697778

698779
#[lang = "u16"]
699780
impl u16 {
781+
widening_impl! { u16, u32, 16 }
700782
uint_impl! { u16, u16, 16, 65535, 4, "0xa003", "0x3a", "0x1234", "0x3412", "0x2c48",
701783
"[0x34, 0x12]", "[0x12, 0x34]", "", "" }
702784
}
703785

704786
#[lang = "u32"]
705787
impl u32 {
788+
widening_impl! { u32, u64, 32 }
706789
uint_impl! { u32, u32, 32, 4294967295, 8, "0x10000b3", "0xb301", "0x12345678",
707790
"0x78563412", "0x1e6a2c48", "[0x78, 0x56, 0x34, 0x12]", "[0x12, 0x34, 0x56, 0x78]", "", "" }
708791
}
709792

710793
#[lang = "u64"]
711794
impl u64 {
795+
widening_impl! { u64, u128, 64 }
712796
uint_impl! { u64, u64, 64, 18446744073709551615, 12, "0xaa00000000006e1", "0x6e10aa",
713797
"0x1234567890123456", "0x5634129078563412", "0x6a2c48091e6a2c48",
714798
"[0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]",
@@ -731,13 +815,15 @@ impl u128 {
731815
#[cfg(target_pointer_width = "16")]
732816
#[lang = "usize"]
733817
impl usize {
818+
widening_impl! { usize, u32, 16 }
734819
uint_impl! { usize, u16, 16, 65535, 4, "0xa003", "0x3a", "0x1234", "0x3412", "0x2c48",
735820
"[0x34, 0x12]", "[0x12, 0x34]",
736821
usize_isize_to_xe_bytes_doc!(), usize_isize_from_xe_bytes_doc!() }
737822
}
738823
#[cfg(target_pointer_width = "32")]
739824
#[lang = "usize"]
740825
impl usize {
826+
widening_impl! { usize, u64, 32 }
741827
uint_impl! { usize, u32, 32, 4294967295, 8, "0x10000b3", "0xb301", "0x12345678",
742828
"0x78563412", "0x1e6a2c48", "[0x78, 0x56, 0x34, 0x12]", "[0x12, 0x34, 0x56, 0x78]",
743829
usize_isize_to_xe_bytes_doc!(), usize_isize_from_xe_bytes_doc!() }
@@ -746,6 +832,7 @@ impl usize {
746832
#[cfg(target_pointer_width = "64")]
747833
#[lang = "usize"]
748834
impl usize {
835+
widening_impl! { usize, u128, 64 }
749836
uint_impl! { usize, u64, 64, 18446744073709551615, 12, "0xaa00000000006e1", "0x6e10aa",
750837
"0x1234567890123456", "0x5634129078563412", "0x6a2c48091e6a2c48",
751838
"[0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]",

library/core/src/num/uint_macros.rs

+60
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,36 @@ macro_rules! uint_impl {
14081408
(a as Self, b)
14091409
}
14101410

1411+
/// Calculates `self + rhs + carry` without the ability to overflow.
1412+
///
1413+
/// Performs "ternary addition" which takes in an extra bit to add, and may return an
1414+
/// additional bit of overflow. This allows for chaining together multiple additions
1415+
/// to create "big integers" which represent larger values.
1416+
///
1417+
/// # Examples
1418+
///
1419+
/// Basic usage
1420+
///
1421+
/// ```
1422+
/// #![feature(bigint_helper_methods)]
1423+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".carrying_add(2, false), (7, false));")]
1424+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".carrying_add(2, true), (8, false));")]
1425+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.carrying_add(1, false), (0, true));")]
1426+
#[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.carrying_add(1, true), (1, true));")]
1427+
/// ```
1428+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
1429+
#[rustc_const_unstable(feature = "const_bigint_helper_methods", issue = "85532")]
1430+
#[must_use = "this returns the result of the operation, \
1431+
without modifying the original"]
1432+
#[inline]
1433+
pub const fn carrying_add(self, rhs: Self, carry: bool) -> (Self, bool) {
1434+
// note: longer-term this should be done via an intrinsic, but this has been shown
1435+
// to generate optimal code for now, and LLVM doesn't have an equivalent intrinsic
1436+
let (a, b) = self.overflowing_add(rhs);
1437+
let (c, d) = a.overflowing_add(carry as $SelfT);
1438+
(c, b | d)
1439+
}
1440+
14111441
/// Calculates `self` - `rhs`
14121442
///
14131443
/// Returns a tuple of the subtraction along with a boolean indicating
@@ -1433,6 +1463,36 @@ macro_rules! uint_impl {
14331463
(a as Self, b)
14341464
}
14351465

1466+
/// Calculates `self - rhs - borrow` without the ability to overflow.
1467+
///
1468+
/// Performs "ternary subtraction" which takes in an extra bit to subtract, and may return
1469+
/// an additional bit of overflow. This allows for chaining together multiple subtractions
1470+
/// to create "big integers" which represent larger values.
1471+
///
1472+
/// # Examples
1473+
///
1474+
/// Basic usage
1475+
///
1476+
/// ```
1477+
/// #![feature(bigint_helper_methods)]
1478+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".borrowing_sub(2, false), (3, false));")]
1479+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".borrowing_sub(2, true), (2, false));")]
1480+
#[doc = concat!("assert_eq!(0", stringify!($SelfT), ".borrowing_sub(1, false), (", stringify!($SelfT), "::MAX, true));")]
1481+
#[doc = concat!("assert_eq!(0", stringify!($SelfT), ".borrowing_sub(1, true), (", stringify!($SelfT), "::MAX - 1, true));")]
1482+
/// ```
1483+
#[unstable(feature = "bigint_helper_methods", issue = "85532")]
1484+
#[rustc_const_unstable(feature = "const_bigint_helper_methods", issue = "85532")]
1485+
#[must_use = "this returns the result of the operation, \
1486+
without modifying the original"]
1487+
#[inline]
1488+
pub const fn borrowing_sub(self, rhs: Self, borrow: bool) -> (Self, bool) {
1489+
// note: longer-term this should be done via an intrinsic, but this has been shown
1490+
// to generate optimal code for now, and LLVM doesn't have an equivalent intrinsic
1491+
let (a, b) = self.overflowing_sub(rhs);
1492+
let (c, d) = a.overflowing_sub(borrow as $SelfT);
1493+
(c, b | d)
1494+
}
1495+
14361496
/// Calculates the multiplication of `self` and `rhs`.
14371497
///
14381498
/// Returns a tuple of the multiplication along with a boolean

0 commit comments

Comments
 (0)