Skip to content
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

Add widening_mul, group absolute value operations, reformat. #1

Merged
merged 6 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target
proptest-regressions

.idea/
61 changes: 60 additions & 1 deletion benches/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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);
30 changes: 2 additions & 28 deletions src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod from;
mod mul;
mod neg;
mod resize;
mod sign;
mod sub;

#[cfg(feature = "rand_core")]
Expand Down Expand Up @@ -73,25 +74,11 @@ impl<const LIMBS: usize> Int<LIMBS> {

/// 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<LIMBS>) -> Self {
Self(value)
}

/// Construct new [`Int`] from a sign and magnitude.
/// Returns `None` when the magnitude does not fit in an [`Int<LIMBS>`].
pub const fn new_from_sign_and_magnitude(
is_negative: ConstChoice,
magnitude: Uint<LIMBS>,
) -> ConstCtOption<Self> {
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]
Expand Down Expand Up @@ -160,19 +147,6 @@ impl<const LIMBS: usize> Int<LIMBS> {
Self::eq(self, &Self::MAX)
}

/// The sign and magnitude of this [`Int`].
pub const fn sign_and_magnitude(&self) -> (ConstChoice, Uint<LIMBS>) {
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<LIMBS> {
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))
Expand Down
32 changes: 14 additions & 18 deletions src/int/div.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ impl<const LIMBS: usize> Int<LIMBS> {
/// 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<Self>,
) -> (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::<Uint<LIMBS>>::new_unwrap(rhs_mag));

(quotient, remainder, lhs_sgn, rhs_sgn)
}
Expand All @@ -46,15 +46,15 @@ impl<const LIMBS: usize> Int<LIMBS> {
/// 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<Self>,
) -> (ConstCtOption<Self>, ConstCtOption<Self>) {
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),
)
}

Expand Down Expand Up @@ -99,7 +99,7 @@ impl<const LIMBS: usize> Int<LIMBS> {
let quotient_sub_one = quotient.wrapping_add(&Uint::ONE);
let quotient = Uint::select(&quotient, &quotient_sub_one, increment_quotient);

Self::new_from_sign_and_magnitude(opposing_signs, quotient).into()
Self::new_from_abs_sign(quotient, opposing_signs).into()
})
}

Expand Down Expand Up @@ -128,8 +128,8 @@ impl<const LIMBS: usize> Int<LIMBS> {
/// );
/// ```
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);
Expand All @@ -145,14 +145,10 @@ impl<const LIMBS: usize> Int<LIMBS> {
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)))
})
})
}
}
Expand Down
112 changes: 106 additions & 6 deletions src/int/mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<const LIMBS: usize> Int<LIMBS> {
/// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`.
Expand All @@ -17,11 +17,11 @@ impl<const LIMBS: usize> Int<LIMBS> {
rhs: &Int<RHS_LIMBS>,
) -> (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.
Expand All @@ -31,13 +31,30 @@ impl<const LIMBS: usize> Int<LIMBS> {

(lo, hi, negate)
}

/// Multiply `self` by `rhs`, returning a concatenated "wide" result.
pub const fn widening_mul<const RHS_LIMBS: usize, const WIDE_LIMBS: usize>(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have widening_mul, could be nice to implement Mul<Int<RHS_LIMBS>> for Int<LIMBS> similarly to Uint. This was done by me at the time using macros, the idea was for the standard type to have Mul<Int128> for Int192 and such.

&self,
rhs: &Int<RHS_LIMBS>,
) -> Int<WIDE_LIMBS>
where
Uint<LIMBS>: ConcatMixed<Uint<RHS_LIMBS>, MixedOutput = Uint<WIDE_LIMBS>>,
{
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))
erik-3milabs marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<const LIMBS: usize, const RHS_LIMBS: usize> CheckedMul<Int<RHS_LIMBS>> for Int<LIMBS> {
#[inline]
fn checked_mul(&self, rhs: &Int<RHS_LIMBS>) -> CtOption<Self> {
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()))
}
}
Expand Down Expand Up @@ -87,10 +104,24 @@ impl<const LIMBS: usize> MulAssign<&Checked<Int<LIMBS>>> for Checked<Int<LIMBS>>
}
}

// TODO(lleoha): unfortunately we cannot satisfy this (yet!).
// impl<const LIMBS: usize, const RHS_LIMBS: usize, const WIDE_LIMBS: usize>
// WideningMul<Int<RHS_LIMBS>> for Int<LIMBS>
// where
// Uint<LIMBS>: ConcatMixed<Uint<RHS_LIMBS>, MixedOutput = Uint<WIDE_LIMBS>>,
// {
// type Output = Int<WIDE_LIMBS>;
//
// #[inline]
// fn widening_mul(&self, rhs: Int<RHS_LIMBS>) -> 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() {
Expand Down Expand Up @@ -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")
);
}
}
Loading