From b190decb97ae6c003f27c39f77b7010bac33a608 Mon Sep 17 00:00:00 2001 From: Trevor Spiteri Date: Wed, 28 Feb 2018 00:22:41 +0100 Subject: [PATCH] two's-complement logic operations on BigInt --- src/bigint.rs | 385 +++++++++++++++++++++++++++++++++++++++- src/biguint.rs | 20 ++- tests/bigint_bitwise.rs | 107 +++++++++++ 3 files changed, 509 insertions(+), 3 deletions(-) create mode 100644 tests/bigint_bitwise.rs diff --git a/src/bigint.rs b/src/bigint.rs index cdd68001..e471b8b9 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -1,5 +1,6 @@ use std::default::Default; -use std::ops::{Add, Div, Mul, Neg, Rem, Shl, Shr, Sub, Not}; +use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub, + BitAndAssign, BitOrAssign, BitXorAssign, Not}; use std::str::{self, FromStr}; use std::fmt; use std::cmp::Ordering::{self, Less, Greater, Equal}; @@ -24,7 +25,7 @@ use traits::{ToPrimitive, FromPrimitive, Num, CheckedAdd, CheckedSub, use self::Sign::{Minus, NoSign, Plus}; use super::ParseBigIntError; -use super::big_digit::{BigDigit, DoubleBigDigit}; +use super::big_digit::{self, BigDigit, DoubleBigDigit}; use biguint; use biguint::to_str_radix_reversed; use biguint::BigUint; @@ -179,6 +180,386 @@ impl fmt::UpperHex for BigInt { } } +#[inline] +fn negate_carry(a: BigDigit, acc: &mut DoubleBigDigit) -> BigDigit { + *acc += (!a) as DoubleBigDigit; + let lo = *acc as BigDigit; + *acc >>= big_digit::BITS; + lo +} + +fn normalize(i: &mut BigInt) { + biguint::normalize(&mut i.data); + if i.data.is_zero() { + i.sign = Sign::NoSign; + } +} + +// ! 0 = !...0000 = ...ffff - ...0000 = ...ffff = -1 +// !+1 = !...0001 = ...ffff - ...0001 = ...fffe = -2 +// !-2 = !...fffe = ...ffff - ...fffe = ...0001 = +1 +// !-1 = !...ffff = ...ffff - ...ffff = ...0000 = 0 +impl Not for BigInt { + type Output = BigInt; + + fn not(mut self) -> BigInt { + match self.sign { + Sign::NoSign | Sign::Plus => { + self.data += 1 as BigDigit; + self.sign = Sign::Minus; + } + Sign::Minus => { + self.data -= 1 as BigDigit; + self.sign = if self.data.is_zero() { + Sign::NoSign + } else { + Sign::Plus + }; + } + } + self + } +} + +// +1 & +ff = ...0001 & ...00ff = 0001 = +1 +// +ff & +1 = ...00ff & ...0001 = 0001 = +1 +// answer is pos, has length of shortest +fn bitand_pos_pos(a: &mut Vec, b: &Vec) { + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + *ai &= bi; + } + a.truncate(b.len()); +} + +// +1 & -ff = ...0001 & ...ff01 = ...0001 = +1 +// +ff & -1 = ...00ff & ...ffff = ...00ff = +ff +// answer is pos, has length of a +fn bitand_pos_neg(a: &mut Vec, b: &Vec) { + let mut carry_b = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_b = negate_carry(bi, &mut carry_b); + *ai &= abs_b; + } +} + +// -1 & +ff = ...ffff & ...00ff = ...00ff = +ff +// -ff & +1 = ...ff01 & ...0001 = ...0001 = +1 +// answer is pos, has length of b +fn bitand_neg_pos(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + *ai = abs_a & bi; + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().cloned()); + } else { + a.truncate(b.len()); + } +} + +// -1 & -ff = ...ffff & ...ff01 = ...ff01 = -ff +// -ff & -1 = ...ff01 & ...ffff = ...ff01 = -ff +// answer is neg, has length of longest +fn bitand_neg_neg(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + let mut carry_b = 1; + let mut carry_and = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + let abs_b = negate_carry(bi, &mut carry_b); + *ai = negate_carry(abs_a & abs_b, &mut carry_and); + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().map(|&bi| { + let abs_b = negate_carry(bi, &mut carry_b); + negate_carry(abs_b, &mut carry_and) + })); + } else { + for ai in a[b.len()..].iter_mut() { + let abs_a = negate_carry(*ai, &mut carry_a); + *ai = negate_carry(abs_a, &mut carry_and); + } + } +} + +impl<'a> BitAnd<&'a BigInt> for BigInt { + type Output = BigInt; + + fn bitand(mut self, other: &BigInt) -> BigInt { + self &= other; + self + } +} + +impl<'a> BitAndAssign<&'a BigInt> for BigInt { + fn bitand_assign(&mut self, other: &BigInt) { + match (self.sign, other.sign) { + (Sign::NoSign, Sign::NoSign) => { + // do nothing + } + (Sign::Minus, Sign::Minus) => { + bitand_neg_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (_, Sign::Minus) => { + bitand_pos_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + (Sign::Minus, _) => { + bitand_neg_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + (_, _) => { + bitand_pos_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + } + normalize(self); + } +} + +// +1 | +ff = ...0001 | ...00ff = 00ff = +ff +// +ff | +1 = ...00ff | ...0001 = 00ff = +ff +// answer is pos, has length of longest +fn bitor_pos_pos(a: &mut Vec, b: &Vec) { + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + *ai |= bi; + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().cloned()); + } +} + +// +1 | -ff = ...0001 | ...ff01 = ...ff01 = -ff +// +ff | -1 = ...00ff | ...ffff = ...ffff = -1 +// answer is neg, has length of b +fn bitor_pos_neg(a: &mut Vec, b: &Vec) { + let mut carry_b = 1; + let mut carry_or = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_b = negate_carry(bi, &mut carry_b); + *ai = negate_carry(*ai | abs_b, &mut carry_or); + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().map(|&bi| { + let abs_b = negate_carry(bi, &mut carry_b); + negate_carry(abs_b, &mut carry_or) + })); + } else { + a.truncate(b.len()); + } +} + +// -1 | +ff = ...ffff | ...00ff = ...ffff = -1 +// -ff | +1 = ...ff01 | ...0001 = ...ff01 = -ff +// answer is neg, has length of a +fn bitor_neg_pos(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + let mut carry_or = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + *ai = negate_carry(abs_a | bi, &mut carry_or); + } +} + +// -1 | -ff = ...ffff | ...ff01 = ...ffff = -1 +// -ff | -1 = ...ff01 | ...ffff = ...ffff = -1 +// answer is neg, has length of shortest +fn bitor_neg_neg(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + let mut carry_b = 1; + let mut carry_or = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + let abs_b = negate_carry(bi, &mut carry_b); + *ai = negate_carry(abs_a | abs_b, &mut carry_or); + } + a.truncate(b.len()); +} + +impl<'a> BitOr<&'a BigInt> for BigInt { + type Output = BigInt; + + fn bitor(mut self, other: &BigInt) -> BigInt { + self |= other; + self + } +} + +impl<'a> BitOrAssign<&'a BigInt> for BigInt { + fn bitor_assign(&mut self, other: &BigInt) { + match (self.sign, other.sign) { + (Sign::NoSign, Sign::NoSign) => { + // do nothing + } + (Sign::Minus, Sign::Minus) => { + bitor_neg_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (_, Sign::Minus) => { + bitor_pos_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (Sign::Minus, _) => { + bitor_neg_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (_, _) => { + bitor_pos_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + } + normalize(self); + } +} + +// +1 ^ +ff = ...0001 ^ ...00ff = 00fe = +fe +// +ff ^ +1 = ...00ff ^ ...0001 = 00fe = +fe +// answer is pos, has length of longest +fn bitxor_pos_pos(a: &mut Vec, b: &Vec) { + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + *ai ^= bi; + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().cloned()); + } +} + +// +1 ^ -ff = ...0001 ^ ...ff01 = ...ff00 = -100 +// +ff ^ -1 = ...00ff ^ ...ffff = ...ff00 = -100 +// answer is neg, has length of longest with a possible carry +fn bitxor_pos_neg(a: &mut Vec, b: &Vec) { + let mut carry_b = 1; + let mut carry_xor = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_b = negate_carry(bi, &mut carry_b); + *ai = negate_carry(*ai ^ abs_b, &mut carry_xor); + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().map(|&bi| { + let abs_b = negate_carry(bi, &mut carry_b); + negate_carry(abs_b, &mut carry_xor) + })); + } else { + for ai in a[b.len()..].iter_mut() { + let abs_b = negate_carry(0, &mut carry_b); + *ai = negate_carry(*ai ^ abs_b, &mut carry_xor); + } + } + if carry_xor ^ carry_b != 0 { + a.push(1); + } +} + +// -1 ^ +ff = ...ffff ^ ...00ff = ...ff00 = -100 +// -ff ^ +1 = ...ff01 ^ ...0001 = ...ff00 = -100 +// answer is neg, has length of longest with a possible carry +fn bitxor_neg_pos(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + let mut carry_xor = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + *ai = negate_carry(abs_a ^ bi, &mut carry_xor); + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().map(|&bi| { + let abs_a = negate_carry(0, &mut carry_a); + negate_carry(abs_a ^ bi, &mut carry_xor) + })); + } else { + for ai in a[b.len()..].iter_mut() { + let abs_a = negate_carry(*ai, &mut carry_a); + *ai = negate_carry(abs_a, &mut carry_xor); + } + } + if carry_xor ^ carry_a > 0 { + a.push(1); + } +} + +// -1 ^ -ff = ...ffff ^ ...ff01 = ...00fe = +fe +// -ff & -1 = ...ff01 ^ ...ffff = ...00fe = +fe +// answer is pos, has length of longest +fn bitxor_neg_neg(a: &mut Vec, b: &Vec) { + let mut carry_a = 1; + let mut carry_b = 1; + for (ai, &bi) in a.iter_mut().zip(b.iter()) { + let abs_a = negate_carry(*ai, &mut carry_a); + let abs_b = negate_carry(bi, &mut carry_b); + *ai = abs_a ^ abs_b; + } + if b.len() > a.len() { + let extra = &b[a.len()..]; + a.extend(extra.iter().map(|&bi| { + let abs_a = negate_carry(0, &mut carry_a); + let abs_b = negate_carry(bi, &mut carry_b); + abs_a ^ abs_b + })); + } else { + for ai in a[b.len()..].iter_mut() { + let abs_a = negate_carry(*ai, &mut carry_a); + let abs_b = negate_carry(0, &mut carry_b); + *ai = abs_a ^ abs_b; + } + } +} + +impl<'a> BitXor<&'a BigInt> for BigInt { + type Output = BigInt; + + fn bitxor(mut self, other: &BigInt) -> BigInt { + self ^= other; + self + } +} + +impl<'a> BitXorAssign<&'a BigInt> for BigInt { + fn bitxor_assign(&mut self, other: &BigInt) { + match (self.sign, other.sign) { + (Sign::NoSign, Sign::NoSign) => { + // do nothing + } + (Sign::Minus, Sign::Minus) => { + bitxor_neg_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + (_, Sign::Minus) => { + bitxor_pos_neg(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (Sign::Minus, _) => { + bitxor_neg_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Minus; + } + (_, _) => { + bitxor_pos_pos(biguint::digits_mut(&mut self.data), + biguint::digits(&other.data)); + self.sign = Sign::Plus; + } + } + normalize(self); + } +} + impl FromStr for BigInt { type Err = ParseBigIntError; diff --git a/src/biguint.rs b/src/biguint.rs index c7b06790..3eb26b0e 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -22,7 +22,7 @@ use traits::{ToPrimitive, FromPrimitive, Float, Num, Unsigned, CheckedAdd, Check CheckedDiv, Zero, One}; #[path = "algorithms.rs"] -mod algorithms; +pub mod algorithms; #[path = "monty.rs"] mod monty; pub use self::algorithms::big_digit; @@ -1686,6 +1686,24 @@ pub fn trailing_zeros(u: &BigUint) -> Option { .map(|(i, digit)| i * big_digit::BITS + digit.trailing_zeros() as usize) } +// needed since we cannot set visibility of `BigUint::data` to `pub(crate)` +#[inline] +pub fn digits(u: &BigUint) -> &Vec { + &u.data +} + +// needed since we cannot set visibility of `BigUint::data` to `pub(crate)` +#[inline] +pub fn digits_mut(u: &mut BigUint) -> &mut Vec { + &mut u.data +} + +// needed since we cannot set visibility of `BigUint::normalize` to `pub(crate)` +#[inline] +pub fn normalize(u: &mut BigUint) { + u.normalize() +} + impl_sum_iter_type!(BigUint); impl_product_iter_type!(BigUint); diff --git a/tests/bigint_bitwise.rs b/tests/bigint_bitwise.rs new file mode 100644 index 00000000..40ad0ca7 --- /dev/null +++ b/tests/bigint_bitwise.rs @@ -0,0 +1,107 @@ +extern crate num_bigint; +extern crate num_traits; + +use num_bigint::{BigDigit, BigInt, Sign, ToBigInt}; +use num_traits::ToPrimitive; + +enum ValueVec { + N, + P(&'static [BigDigit]), + M(&'static [BigDigit]), +} + +use ValueVec::*; + +impl ToBigInt for ValueVec { + fn to_bigint(&self) -> Option { + match self { + &N => Some(BigInt::from_slice(Sign::NoSign, &[])), + &P(s) => Some(BigInt::from_slice(Sign::Plus, s)), + &M(s) => Some(BigInt::from_slice(Sign::Minus, s)), + } + } +} + +// a, b, a & b, a | b, a ^ b +const BITWISE_VALUES: &'static [(ValueVec, ValueVec, ValueVec, ValueVec, ValueVec)] + = &[(N, N, N, N, N), + + (N, P(&[1]), N, P(&[1]), P(&[1])), + (N, P(&[!0]), N, P(&[!0]), P(&[!0])), + (N, P(&[0, 1]), N, P(&[0, 1]), P(&[0, 1])), + + (N, M(&[1]), N, M(&[1]), M(&[1])), + (N, M(&[!0]), N, M(&[!0]), M(&[!0])), + (N, M(&[0, 1]), N, M(&[0, 1]), M(&[0, 1])), + + (P(&[1]), P(&[!0]), P(&[1]), P(&[!0]), P(&[!0 - 1])), + (P(&[!0]), P(&[!0]), P(&[!0]), P(&[!0]), N), + (P(&[!0]), P(&[1, 1]), P(&[1]), P(&[!0, 1]), P(&[!0 - 1, 1])), + + (P(&[1]), M(&[!0]), P(&[1]), M(&[!0]), M(&[0, 1])), + (P(&[!0]), M(&[1]), P(&[!0]), M(&[1]), M(&[0, 1])), + (P(&[!0]), M(&[!0]), P(&[1]), M(&[1]), M(&[2])), + (P(&[!0]), M(&[1, 1]), P(&[!0]), M(&[1, 1]), M(&[0, 2])), + (P(&[1, 1]), M(&[!0]), P(&[1, 1]), M(&[!0]), M(&[0, 2])), + + (M(&[1]), M(&[!0]), M(&[!0]), M(&[1]), P(&[!0 - 1])), + (M(&[!0]), M(&[!0]), M(&[!0]), M(&[!0]), N), + (M(&[!0]), M(&[1, 1]), M(&[!0, 1]), M(&[1]), P(&[!0 - 1, 1]))]; + +#[test] +fn test_bitwise() { + for &(ref a, ref b, ref and, ref or, ref xor) in BITWISE_VALUES.iter() { + let a = a.to_bigint().unwrap(); + let b = b.to_bigint().unwrap(); + let and = and.to_bigint().unwrap(); + let or = or.to_bigint().unwrap(); + let xor = xor.to_bigint().unwrap(); + + // sanity check for tests that fit in i64 + if let (Some(pa), Some(pb)) = (a.to_i64(), b.to_i64()) { + if let Some(pand) = and.to_i64() { + assert_eq!(pa & pb, pand); + } + if let Some(por) = or.to_i64() { + assert_eq!(pa | pb, por); + } + if let Some(pxor) = xor.to_i64() { + assert_eq!(pa ^ pb, pxor); + } + } + + assert_eq!(a.clone() & &b, and, "{:x} & {:x}", a, b); + assert_eq!(b.clone() & &a, and, "{:x} & {:x}", b, a); + assert_eq!(a.clone() | &b, or, "{:x} | {:x}", a, b); + assert_eq!(b.clone() | &a, or, "{:x} | {:x}", b, a); + assert_eq!(a.clone() ^ &b, xor, "{:x} ^ {:x}", a, b); + assert_eq!(b.clone() ^ &a, xor, "{:x} ^ {:x}", b, a); + } +} + +// a, !a +const NOT_VALUES: &'static [(ValueVec, ValueVec)] + = &[(N, M(&[1])), + (P(&[1]), M(&[2])), + (P(&[2]), M(&[3])), + (P(&[!0 - 2]), M(&[!0 - 1])), + (P(&[!0 - 1]), M(&[!0])), + (P(&[!0]), M(&[0, 1])), + (P(&[0, 1]), M(&[1, 1])), + (P(&[1, 1]), M(&[2, 1]))]; + +#[test] +fn test_not() { + for &(ref a, ref not) in NOT_VALUES.iter() { + let a = a.to_bigint().unwrap(); + let not = not.to_bigint().unwrap(); + + // sanity check for tests that fit in i64 + if let (Some(pa), Some(pnot)) = (a.to_i64(), not.to_i64()) { + assert_eq!(!pa, pnot); + } + + assert_eq!(!a.clone(), not, "!{:x}", a); + assert_eq!(!not.clone(), a, "!{:x}", not); + } +}