diff --git a/.gitignore b/.gitignore index ea7e3f81..5ea04312 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ target proptest-regressions + +.idea/ diff --git a/benches/int.rs b/benches/int.rs index 4108c46f..466eb6b7 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -58,6 +58,58 @@ fn bench_mul(c: &mut Criterion) { }); } +fn bench_widening_mul(c: &mut Criterion) { + let mut group = c.benchmark_group("widening ops"); + + group.bench_function("widening_mul, I128xI128", |b| { + b.iter_batched( + || (I128::random(&mut OsRng), I128::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I256xI256", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I512xI512", |b| { + b.iter_batched( + || (I512::random(&mut OsRng), I512::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I1024xI1024", |b| { + b.iter_batched( + || (I1024::random(&mut OsRng), I1024::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I2048xI2048", |b| { + b.iter_batched( + || (I2048::random(&mut OsRng), I2048::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I4096xI4096", |b| { + b.iter_batched( + || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + fn bench_div(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); @@ -280,6 +332,13 @@ fn bench_sub(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_mul, bench_div, bench_add, bench_sub,); +criterion_group!( + benches, + bench_mul, + bench_widening_mul, + bench_div, + bench_add, + bench_sub, +); criterion_main!(benches); diff --git a/src/int.rs b/src/int.rs index ffbdb6a3..0b338551 100644 --- a/src/int.rs +++ b/src/int.rs @@ -22,6 +22,7 @@ mod from; mod mul; mod neg; mod resize; +mod sign; mod sub; #[cfg(feature = "rand_core")] @@ -73,25 +74,11 @@ impl Int { /// Const-friendly [`Int`] constructor. /// Note: interprets the `value` as an `Int`. For a proper conversion, - /// see [`Int::new_from_sign_and_magnitude`]. + /// see [`Int::new_from_abs_sign`]. pub const fn new_from_uint(value: Uint) -> Self { Self(value) } - /// Construct new [`Int`] from a sign and magnitude. - /// Returns `None` when the magnitude does not fit in an [`Int`]. - pub const fn new_from_sign_and_magnitude( - is_negative: ConstChoice, - magnitude: Uint, - ) -> ConstCtOption { - ConstCtOption::new( - Self(magnitude).wrapping_neg_if(is_negative), - Uint::gt(&magnitude, &Int::MAX.0) - .not() - .or(is_negative.and(Uint::eq(&magnitude, &Int::MIN.0))), - ) - } - /// Create an [`Int`] from an array of [`Word`]s (i.e. word-sized unsigned /// integers). #[inline] @@ -160,19 +147,6 @@ impl Int { Self::eq(self, &Self::MAX) } - /// The sign and magnitude of this [`Int`]. - pub const fn sign_and_magnitude(&self) -> (ConstChoice, Uint) { - let sign = self.is_negative(); - // Note: this negate_if is safe to use, since we are negating based on self.is_negative() - let magnitude = self.wrapping_neg_if(sign); - (sign, magnitude.0) - } - - /// The magnitude of this [`Int`]. - pub const fn magnitude(&self) -> Uint { - self.sign_and_magnitude().1 - } - /// Invert the most significant bit (msb) of this [`Int`] const fn invert_msb(&self) -> Self { Self(self.0.bitxor(&Self::SIGN_MASK.0)) diff --git a/src/int/div.rs b/src/int/div.rs index a892131b..41d84c9a 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -11,17 +11,17 @@ impl Int { /// Base div_rem operation. /// Given `(a, b)`, computes the quotient and remainder of their absolute values. Furthermore, /// returns the signs of `a` and `b`. - fn div_rem_base( + const fn div_rem_base( &self, rhs: &NonZero, ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { // Step 1: split operands into signs and magnitudes. - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.0.sign_and_magnitude(); + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.0.abs_sign(); // Step 2. Divide magnitudes // safe to unwrap since rhs is NonZero. - let (quotient, remainder) = lhs_mag.div_rem(&NonZero::new(rhs_mag).unwrap()); + let (quotient, remainder) = lhs_mag.div_rem(&NonZero::>::new_unwrap(rhs_mag)); (quotient, remainder, lhs_sgn, rhs_sgn) } @@ -46,15 +46,15 @@ impl Int { /// assert_eq!(quotient.unwrap(), I128::from(2)); /// assert_eq!(remainder.unwrap(), I128::from(-2)); /// ``` - pub fn checked_div_rem( + pub const fn checked_div_rem( &self, rhs: &NonZero, ) -> (ConstCtOption, ConstCtOption) { let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(rhs); let opposing_signs = lhs_sgn.ne(rhs_sgn); ( - Self::new_from_sign_and_magnitude(opposing_signs, quotient), - Self::new_from_sign_and_magnitude(lhs_sgn, remainder), + Self::new_from_abs_sign(quotient, opposing_signs), + Self::new_from_abs_sign(remainder, lhs_sgn), ) } @@ -99,7 +99,7 @@ impl Int { let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); let quotient = Uint::select("ient, "ient_sub_one, increment_quotient); - Self::new_from_sign_and_magnitude(opposing_signs, quotient).into() + Self::new_from_abs_sign(quotient, opposing_signs).into() }) } @@ -128,8 +128,8 @@ impl Int { /// ); /// ``` pub fn checked_div_mod_floor(&self, rhs: &Self) -> CtOption<(Self, Self)> { - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.abs_sign(); let opposing_signs = lhs_sgn.xor(rhs_sgn); NonZero::new(rhs_mag).and_then(|rhs_mag| { let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); @@ -145,14 +145,10 @@ impl Int { let inv_remainder = rhs_mag.wrapping_sub(&remainder); let remainder = Uint::select(&remainder, &inv_remainder, modify); - CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, quotient)).and_then( - |quotient| { - CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, remainder)) - .and_then(|remainder| { - CtOption::new((quotient, remainder), Choice::from(1u8)) - }) - }, - ) + CtOption::from(Int::new_from_abs_sign(quotient, opposing_signs)).and_then(|quotient| { + CtOption::from(Int::new_from_abs_sign(remainder, opposing_signs)) + .and_then(|remainder| CtOption::new((quotient, remainder), Choice::from(1u8))) + }) }) } } diff --git a/src/int/mul.rs b/src/int/mul.rs index 4edd2bfd..9e692c40 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConstChoice, Int, Uint, Zero}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. @@ -17,11 +17,11 @@ impl Int { rhs: &Int, ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, ConstChoice) { // Step 1: split operands into their signs and magnitudes. - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let (lhs_abs, lhs_sgn) = self.abs_sign(); + let (rhs_abs, rhs_sgn) = rhs.abs_sign(); // Step 2: multiply the magnitudes - let (lo, hi) = lhs_mag.split_mul(&rhs_mag); + let (lo, hi) = lhs_abs.split_mul(&rhs_abs); // Step 3. Determine if the result should be negated. // This should be done if and only if lhs and rhs have opposing signs. @@ -31,13 +31,30 @@ impl Int { (lo, hi, negate) } + + /// Multiply `self` by `rhs`, returning a concatenated "wide" result. + pub const fn widening_mul( + &self, + rhs: &Int, + ) -> Int + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let (lhs_abs, lhs_sign) = self.abs_sign(); + let (rhs_abs, rhs_sign) = rhs.abs_sign(); + let product_abs = lhs_abs.widening_mul(&rhs_abs); + let product_sign = lhs_sign.xor(rhs_sign); + + // always fits + Int::new_from_uint(product_abs.wrapping_neg_if(product_sign)) + } } impl CheckedMul> for Int { #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); - let val = Self::new_from_sign_and_magnitude(is_negative, lo); + let val = Self::new_from_abs_sign(lo, is_negative); CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } @@ -87,10 +104,24 @@ impl MulAssign<&Checked>> for Checked> } } +// TODO(lleoha): unfortunately we cannot satisfy this (yet!). +// impl +// WideningMul> for Int +// where +// Uint: ConcatMixed, MixedOutput = Uint>, +// { +// type Output = Int; +// +// #[inline] +// fn widening_mul(&self, rhs: Int) -> Self::Output { +// self.widening_mul(&rhs) +// } +// } + #[cfg(test)] mod tests { use crate::int::{Int, I128}; - use crate::CheckedMul; + use crate::{CheckedMul, I256}; #[test] fn test_checked_mul() { @@ -183,4 +214,73 @@ mod tests { let result = I128::MAX.checked_mul(&I128::MAX); assert!(bool::from(result.is_none())); } + + #[test] + fn test_widening_mul() { + assert_eq!( + I128::MIN.widening_mul(&I128::MIN), + I256::from_be_hex("4000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MIN.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MIN.widening_mul(&I128::ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MAX), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MIN), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::MINUS_ONE), I256::ONE); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ONE), I256::MINUS_ONE); + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MAX), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + + assert_eq!(I128::ZERO.widening_mul(&I128::MIN), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MINUS_ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MAX), I256::ZERO); + + assert_eq!( + I128::ONE.widening_mul(&I128::MIN), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!(I128::ONE.widening_mul(&I128::MINUS_ONE), I256::MINUS_ONE); + assert_eq!(I128::ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ONE.widening_mul(&I128::ONE), I256::ONE); + assert_eq!( + I128::ONE.widening_mul(&I128::MAX), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + + assert_eq!( + I128::MAX.widening_mul(&I128::MIN), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + assert_eq!(I128::MAX.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MAX.widening_mul(&I128::ONE), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MAX), + I256::from_be_hex("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000001") + ); + } } diff --git a/src/int/neg.rs b/src/int/neg.rs index e6a8cc80..701e6cef 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -5,11 +5,6 @@ use subtle::CtOption; use crate::{ConstChoice, Int, Word}; impl Int { - /// Whether this [`Int`] is negative, as a `ConstChoice`. - pub const fn is_negative(&self) -> ConstChoice { - ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) - } - /// Perform the two's complement "negate" operation on this [`Int`]: /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. /// @@ -43,24 +38,8 @@ impl Int { #[cfg(test)] mod tests { - use num_traits::ConstZero; - use crate::{ConstChoice, Word, I128}; - - #[test] - fn is_negative() { - assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); - - let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); - - let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); - } + use num_traits::ConstZero; #[test] fn negc() { diff --git a/src/int/sign.rs b/src/int/sign.rs new file mode 100644 index 00000000..e8bd3ed0 --- /dev/null +++ b/src/int/sign.rs @@ -0,0 +1,86 @@ +use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; +use num_traits::ConstZero; + +impl Int { + /// Returns the word of most significant [`Limb`]. + /// For the generative case where the number of limbs is zero, + /// zeroed word is returned (which is semantically correct). + /// This method leaks the limb length of the value, which is also OK. + const fn most_significant_word(&self) -> Word { + if Self::LIMBS == 0 { + Word::ZERO + } else { + self.0.to_words()[LIMBS - 1] + } + } + + /// Construct new [`Int`] from an absolute value and sign. + /// Returns `None` when the absolute value does not fit in an [`Int`]. + pub const fn new_from_abs_sign( + abs: Uint, + is_negative: ConstChoice, + ) -> ConstCtOption { + let magnitude = Self(abs).wrapping_neg_if(is_negative); + let fits = Uint::lte(&abs, &Int::MAX.0).or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); + ConstCtOption::new(magnitude, fits) + } + + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn is_negative(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.most_significant_word()) + } + + /// Whether this [`Int`] is positive, as a `ConstChoice`. + pub const fn is_positive(&self) -> ConstChoice { + self.is_negative().not().and(self.is_nonzero()) + } + + /// The sign and magnitude of this [`Int`]. + pub const fn abs_sign(&self) -> (Uint, ConstChoice) { + let sign = self.is_negative(); + // Note: this negate_if is safe to use, since we are negating based on self.is_negative() + let abs = self.wrapping_neg_if(sign); + (abs.0, sign) + } + + /// The magnitude of this [`Int`]. + pub const fn abs(&self) -> Uint { + self.abs_sign().0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::I128; + + #[test] + fn is_negative() { + assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); + + let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); + + let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); + } + + #[test] + fn is_positive() { + assert_eq!(I128::MIN.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::MINUS_ONE.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ZERO.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_positive(), ConstChoice::TRUE); + assert_eq!(I128::MAX.is_positive(), ConstChoice::TRUE); + + let random_negative = I128::from_be_hex("deadbeefcafebabedeadbeefcafebabe"); + assert_eq!(random_negative.is_positive(), ConstChoice::FALSE); + + let random_positive = I128::from_be_hex("0badc0dedeadc0decafebabedeadcafe"); + assert_eq!(random_positive.is_positive(), ConstChoice::TRUE); + } +}