Skip to content

Commit 061e8c6

Browse files
optimize Int wrapping multiplication
Signed-off-by: Andrew Whitehead <cywolf@gmail.com>
1 parent 47c165b commit 061e8c6

File tree

5 files changed

+94
-32
lines changed

5 files changed

+94
-32
lines changed

src/int.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,17 @@ impl<const LIMBS: usize> Int<LIMBS> {
5656
pub const ONE: Self = Self(Uint::ONE); // Bit sequence (be): 0000....0001
5757

5858
/// The value `-1`
59-
pub const MINUS_ONE: Self = Self::FULL_MASK; // Bit sequence (be): 1111....1111
59+
pub const MINUS_ONE: Self = Self(Uint::MAX); // Bit sequence (be): 1111....1111
6060

6161
/// Smallest value this [`Int`] can express.
62-
pub const MIN: Self = Self(Uint::MAX.bitxor(&Uint::MAX.shr(1u32))); // Bit sequence (be): 1000....0000
62+
pub const MIN: Self = Self::MAX.not(); // Bit sequence (be): 1000....0000
6363

6464
/// Maximum value this [`Int`] can express.
65-
pub const MAX: Self = Self(Uint::MAX.shr(1u32)); // Bit sequence (be): 0111....1111
65+
pub const MAX: Self = Self(Uint::MAX.shr_vartime(1u32)); // Bit sequence (be): 0111....1111
6666

6767
/// Bit mask for the sign bit of this [`Int`].
6868
pub const SIGN_MASK: Self = Self::MIN; // Bit sequence (be): 1000....0000
6969

70-
/// All-one bit mask.
71-
pub const FULL_MASK: Self = Self(Uint::MAX); // Bit sequence (be): 1111...1111
72-
7370
/// Total size of the represented integer in bits.
7471
pub const BITS: u32 = Uint::<LIMBS>::BITS;
7572

@@ -113,7 +110,7 @@ impl<const LIMBS: usize> Int<LIMBS> {
113110
}
114111

115112
/// Borrow the inner limbs as a mutable array of [`Word`]s.
116-
pub fn as_mut_words(&mut self) -> &mut [Word; LIMBS] {
113+
pub const fn as_mut_words(&mut self) -> &mut [Word; LIMBS] {
117114
self.0.as_mut_words()
118115
}
119116

@@ -182,7 +179,7 @@ impl<const LIMBS: usize> Int<LIMBS> {
182179
}
183180

184181
/// Whether this [`Int`] is equal to `Self::MAX`.
185-
pub fn is_max(&self) -> ConstChoice {
182+
pub const fn is_max(&self) -> ConstChoice {
186183
Self::eq(self, &Self::MAX)
187184
}
188185

src/int/mul.rs

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign};
44
use num_traits::WrappingMul;
55
use subtle::CtOption;
66

7-
use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint, Zero};
7+
use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, ConstCtOption, Int, Uint};
88

99
impl<const LIMBS: usize> Int<LIMBS> {
1010
/// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`.
@@ -64,12 +64,26 @@ impl<const LIMBS: usize> Int<LIMBS> {
6464
Int::from_bits(product_abs.wrapping_neg_if(product_sign))
6565
}
6666

67+
/// Multiply `self` by `rhs`, returning a `ConstCtOption` which is `is_some` only if
68+
/// overflow did not occur.
69+
pub const fn checked_mul<const RHS_LIMBS: usize>(
70+
&self,
71+
rhs: &Int<RHS_LIMBS>,
72+
) -> ConstCtOption<Self> {
73+
let (abs_lhs, lhs_sgn) = self.abs_sign();
74+
let (abs_rhs, rhs_sgn) = rhs.abs_sign();
75+
let maybe_res = abs_lhs.checked_mul(&abs_rhs);
76+
let (lo, is_some) = maybe_res.components_ref();
77+
Self::new_from_abs_sign(*lo, lhs_sgn.xor(rhs_sgn)).and_choice(is_some)
78+
}
79+
6780
/// Multiply `self` by `rhs`, wrapping the result in case of overflow.
81+
/// This is equivalent to `(self * rhs) % (Uint::<LIMBS>::MAX + 1)`.
6882
pub const fn wrapping_mul<const RHS_LIMBS: usize>(&self, rhs: &Int<RHS_LIMBS>) -> Self {
6983
let (abs_lhs, lhs_sgn) = self.abs_sign();
7084
let (abs_rhs, rhs_sgn) = rhs.abs_sign();
71-
let (lo, _) = abs_lhs.widening_mul(&abs_rhs);
72-
*lo.wrapping_neg_if(lhs_sgn.xor(rhs_sgn)).as_int()
85+
let lo = Self(abs_lhs.wrapping_mul(&abs_rhs));
86+
lo.wrapping_neg_if(lhs_sgn.xor(rhs_sgn))
7387
}
7488
}
7589

@@ -102,9 +116,7 @@ impl<const LIMBS: usize> Int<LIMBS> {
102116
impl<const LIMBS: usize, const RHS_LIMBS: usize> CheckedMul<Int<RHS_LIMBS>> for Int<LIMBS> {
103117
#[inline]
104118
fn checked_mul(&self, rhs: &Int<RHS_LIMBS>) -> CtOption<Self> {
105-
let (lo, hi, is_negative) = self.widening_mul(rhs);
106-
let val = Self::new_from_abs_sign(lo, is_negative);
107-
CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero()))
119+
self.checked_mul(rhs).into()
108120
}
109121
}
110122

@@ -173,7 +185,7 @@ impl<const LIMBS: usize> MulAssign<&Checked<Int<LIMBS>>> for Checked<Int<LIMBS>>
173185

174186
#[cfg(test)]
175187
mod tests {
176-
use crate::{CheckedMul, ConstChoice, I64, I128, I256, Int, U128, U256};
188+
use crate::{ConstChoice, I64, I128, I256, Int, U128, U256};
177189

178190
#[test]
179191
#[allow(clippy::init_numbered_fields)]
@@ -271,20 +283,26 @@ mod tests {
271283
#[test]
272284
fn test_wrapping_mul() {
273285
// wrapping
274-
let a = I128::from_be_hex("FFFFFFFB7B63198EF870DF1F90D9BD9E");
275-
let b = I128::from_be_hex("F20C29FA87B356AA3B4C05C4F9C24B4A");
286+
let a = 0xFFFFFFFB7B63198EF870DF1F90D9BD9Eu128 as i128;
287+
let b = 0xF20C29FA87B356AA3B4C05C4F9C24B4Au128 as i128;
288+
let z = 0xAA700D354D6CF4EE881F8FF8093A19ACu128 as i128;
289+
assert_eq!(a.wrapping_mul(b), z);
276290
assert_eq!(
277-
a.wrapping_mul(&b),
278-
I128::from_be_hex("AA700D354D6CF4EE881F8FF8093A19AC")
291+
I128::from_i128(a).wrapping_mul(&I128::from_i128(b)),
292+
I128::from_i128(z)
279293
);
280294

281295
// no wrapping
282-
let c = I64::from_i64(-12345i64);
296+
let c = -12345i64;
283297
assert_eq!(
284-
a.wrapping_mul(&c),
285-
I128::from_be_hex("0000D9DEF2248095850866CFEBF727D2")
298+
I128::from_i128(a).wrapping_mul(&I128::from_i64(c)),
299+
I128::from_i128(a.wrapping_mul(c as i128))
286300
);
287301

302+
// overflow into MSB
303+
let a = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFu128 as i128;
304+
assert!(!a.is_negative() && a.wrapping_mul(a).is_negative());
305+
288306
// core case
289307
assert_eq!(i8::MAX.wrapping_mul(2), -2);
290308
assert_eq!(i64::MAX.wrapping_mul(2), -2);

src/int/mul_uint.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ impl<const LIMBS: usize> Int<LIMBS> {
9696
&self,
9797
rhs: &Uint<RHS_LIMBS>,
9898
) -> ConstCtOption<Int<LIMBS>> {
99-
let (lo, hi, is_negative) = self.widening_mul_uint(rhs);
100-
Self::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not())
99+
let (abs_lhs, lhs_sgn) = self.abs_sign();
100+
let maybe_lo = abs_lhs.checked_mul(rhs);
101+
let (lo, is_some) = maybe_lo.components_ref();
102+
Self::new_from_abs_sign(*lo, lhs_sgn).and_choice(is_some)
101103
}
102104
}
103105

@@ -136,7 +138,7 @@ impl<const LIMBS: usize, const RHS_LIMBS: usize> Mul<&Uint<RHS_LIMBS>> for &Int<
136138
type Output = Int<LIMBS>;
137139

138140
fn mul(self, rhs: &Uint<RHS_LIMBS>) -> Self::Output {
139-
self.checked_mul(rhs)
141+
self.checked_mul_uint(rhs)
140142
.expect("attempted to multiply with overflow")
141143
}
142144
}

src/int/sign.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ impl<const LIMBS: usize> Int<LIMBS> {
66
/// For the degenerate case where the number of limbs is zero,
77
/// zeroed word is returned (which is semantically correct).
88
/// This method leaks the limb length of the value, which is also OK.
9+
#[inline(always)]
910
const fn most_significant_word(&self) -> Word {
1011
if Self::LIMBS == 0 {
1112
Word::ZERO
@@ -15,17 +16,27 @@ impl<const LIMBS: usize> Int<LIMBS> {
1516
}
1617

1718
/// Construct new [`Int`] from an absolute value and sign.
18-
/// Returns `None` when the absolute value does not fit in an [`Int<LIMBS>`].
19+
/// Returns `None` when the result exceeds the bounds of an [`Int<LIMBS>`].
20+
#[inline]
1921
pub const fn new_from_abs_sign(
2022
abs: Uint<LIMBS>,
2123
is_negative: ConstChoice,
2224
) -> ConstCtOption<Self> {
23-
let magnitude = Self(abs).wrapping_neg_if(is_negative);
24-
let fits = Uint::lte(&abs, &Int::MAX.0).or(is_negative.and(Uint::eq(&abs, &Int::MIN.0)));
25-
ConstCtOption::new(magnitude, fits)
25+
let abs_int = abs.as_int();
26+
let abs_msb = abs_int.is_negative();
27+
let signed = abs_int.wrapping_neg_if(is_negative);
28+
29+
// abs is an acceptable input if the high bit is unset, covering 0..=Int::MAX,
30+
// or if it is equal to Int::MIN (bit sequence '1000...0000') and the sign is negative.
31+
// Int::MIN and zero are the only values for which wrapping negation does not change
32+
// the MSB, so we check if the sign is negative (wrapping negation is performed) and
33+
// the sign of the wrapping negation is also negative.
34+
let fits = abs_msb.not().or(is_negative.and(signed.is_negative()));
35+
ConstCtOption::new(signed, fits)
2636
}
2737

2838
/// Whether this [`Int`] is negative, as a `ConstChoice`.
39+
#[inline(always)]
2940
pub const fn is_negative(&self) -> ConstChoice {
3041
ConstChoice::from_word_msb(self.most_significant_word())
3142
}
@@ -52,7 +63,39 @@ impl<const LIMBS: usize> Int<LIMBS> {
5263
#[cfg(test)]
5364
mod tests {
5465
use super::*;
55-
use crate::I128;
66+
use crate::{I128, U128};
67+
68+
#[test]
69+
fn new_from_abs_sign() {
70+
assert_eq!(
71+
I128::new_from_abs_sign(U128::ZERO, ConstChoice::FALSE).is_some(),
72+
ConstChoice::TRUE
73+
);
74+
assert_eq!(
75+
I128::new_from_abs_sign(U128::ZERO, ConstChoice::TRUE).is_some(),
76+
ConstChoice::TRUE
77+
);
78+
assert_eq!(
79+
I128::new_from_abs_sign(I128::MIN.abs(), ConstChoice::FALSE).is_some(),
80+
ConstChoice::FALSE
81+
);
82+
assert_eq!(
83+
I128::new_from_abs_sign(I128::MIN.abs(), ConstChoice::TRUE).is_some(),
84+
ConstChoice::TRUE
85+
);
86+
assert_eq!(
87+
I128::new_from_abs_sign(I128::MAX.abs(), ConstChoice::FALSE).is_some(),
88+
ConstChoice::TRUE
89+
);
90+
assert_eq!(
91+
I128::new_from_abs_sign(I128::MAX.abs(), ConstChoice::TRUE).is_some(),
92+
ConstChoice::TRUE
93+
);
94+
assert_eq!(
95+
I128::new_from_abs_sign(U128::MAX, ConstChoice::TRUE).is_some(),
96+
ConstChoice::FALSE
97+
);
98+
}
5699

57100
#[test]
58101
fn is_negative() {

src/uint/mul_int.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ impl<const LIMBS: usize> Uint<LIMBS> {
3636
&self,
3737
rhs: &Int<RHS_LIMBS>,
3838
) -> ConstCtOption<Int<LIMBS>> {
39-
let (lo, hi, is_negative) = self.widening_mul_int(rhs);
40-
Int::new_from_abs_sign(lo, is_negative).and_choice(hi.is_nonzero().not())
39+
let (abs_rhs, rhs_sgn) = rhs.abs_sign();
40+
let maybe_res = self.checked_mul(&abs_rhs);
41+
let (lo, is_some) = maybe_res.components_ref();
42+
Int::new_from_abs_sign(*lo, rhs_sgn).and_choice(is_some)
4143
}
4244
}
4345

0 commit comments

Comments
 (0)