diff --git a/ci/big_quickcheck/Cargo.toml b/ci/big_quickcheck/Cargo.toml index d061757f..c58f6d47 100644 --- a/ci/big_quickcheck/Cargo.toml +++ b/ci/big_quickcheck/Cargo.toml @@ -7,9 +7,12 @@ edition = "2018" [dependencies] num-integer = "0.1.42" num-traits = "0.2.11" -quickcheck = "0.9" quickcheck_macros = "0.9" +[dependencies.quickcheck] +version = "0.9" +default-features = false + [dependencies.num-bigint] features = ["quickcheck"] path = "../.." diff --git a/src/bigint.rs b/src/bigint.rs index caa6d0eb..82d43db1 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -3254,6 +3254,129 @@ impl BigInt { pub fn trailing_zeros(&self) -> Option { self.data.trailing_zeros() } + + /// Returns whether the bit in position `bit` is set, + /// using the two's complement for negative numbers + pub fn bit(&self, bit: u64) -> bool { + if self.is_negative() { + // Let the binary representation of a number be + // ... 0 x 1 0 ... 0 + // Then the two's complement is + // ... 1 !x 1 0 ... 0 + // where !x is obtained from x by flipping each bit + if bit >= u64::from(big_digit::BITS) * self.len() as u64 { + true + } else { + let trailing_zeros = self.data.trailing_zeros().unwrap(); + match Ord::cmp(&bit, &trailing_zeros) { + Less => false, + Equal => true, + Greater => !self.data.bit(bit), + } + } + } else { + self.data.bit(bit) + } + } + + /// Sets or clears the bit in the given position, + /// using the two's complement for negative numbers + /// + /// Note that setting/clearing a bit (for positive/negative numbers, + /// respectively) greater than the current bit length, a reallocation + /// may be needed to store the new digits + pub fn set_bit(&mut self, bit: u64, value: bool) { + match self.sign { + Sign::Plus => self.data.set_bit(bit, value), + Sign::NoSign => { + if value { + self.data.set_bit(bit, true); + self.sign = Sign::Plus; + } else { + // Clearing a bit for zero is a no-op + } + } + Sign::Minus => { + let bits_per_digit = u64::from(big_digit::BITS); + if bit >= bits_per_digit * self.len() as u64 { + if !value { + self.data.set_bit(bit, true); + } + } else { + // If the Uint number is + // ... 0 x 1 0 ... 0 + // then the two's complement is + // ... 1 !x 1 0 ... 0 + // |-- bit at position 'trailing_zeros' + // where !x is obtained from x by flipping each bit + let trailing_zeros = self.data.trailing_zeros().unwrap(); + if bit > trailing_zeros { + self.data.set_bit(bit, !value); + } else if bit == trailing_zeros && !value { + // Clearing the bit at position `trailing_zeros` is dealt with by doing + // similarly to what `bitand_neg_pos` does, except we start at digit + // `bit_index`. All digits below `bit_index` are guaranteed to be zero, + // so initially we have `carry_in` = `carry_out` = 1. Furthermore, we + // stop traversing the digits when there are no more carries. + let bit_index = (bit / bits_per_digit).to_usize().unwrap(); + let bit_mask = (1 as BigDigit) << (bit % bits_per_digit); + let mut digit_iter = self.digits_mut().iter_mut().skip(bit_index); + let mut carry_in = 1; + let mut carry_out = 1; + + let digit = digit_iter.next().unwrap(); + let twos_in = negate_carry(*digit, &mut carry_in); + let twos_out = twos_in & !bit_mask; + *digit = negate_carry(twos_out, &mut carry_out); + + for digit in digit_iter { + if carry_in == 0 && carry_out == 0 { + // Exit the loop since no more digits can change + break; + } + let twos = negate_carry(*digit, &mut carry_in); + *digit = negate_carry(twos, &mut carry_out); + } + + if carry_out != 0 { + // All digits have been traversed and there is a carry + debug_assert_eq!(carry_in, 0); + self.digits_mut().push(1); + } + } else if bit < trailing_zeros && value { + // Flip each bit from position 'bit' to 'trailing_zeros', both inclusive + // ... 1 !x 1 0 ... 0 ... 0 + // |-- bit at position 'bit' + // |-- bit at position 'trailing_zeros' + // bit_mask: 1 1 ... 1 0 .. 0 + // This is done by xor'ing with the bit_mask + let index_lo = (bit / bits_per_digit).to_usize().unwrap(); + let index_hi = (trailing_zeros / bits_per_digit).to_usize().unwrap(); + let bit_mask_lo = big_digit::MAX << (bit % bits_per_digit); + let bit_mask_hi = big_digit::MAX + >> (bits_per_digit - 1 - (trailing_zeros % bits_per_digit)); + let digits = self.digits_mut(); + + if index_lo == index_hi { + digits[index_lo] ^= bit_mask_lo & bit_mask_hi; + } else { + digits[index_lo] = bit_mask_lo; + for index in (index_lo + 1)..index_hi { + digits[index] = big_digit::MAX; + } + digits[index_hi] ^= bit_mask_hi; + } + } else { + // We end up here in two cases: + // bit == trailing_zeros && value: Bit is already set + // bit < trailing_zeros && !value: Bit is already cleared + } + } + } + } + // The top bit may have been cleared, so normalize + self.normalize(); + } } impl_sum_iter_type!(BigInt); diff --git a/src/biguint.rs b/src/biguint.rs index 71778cb5..88365aa3 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -2725,6 +2725,43 @@ impl BigUint { pub fn count_ones(&self) -> u64 { self.data.iter().map(|&d| u64::from(d.count_ones())).sum() } + + /// Returns whether the bit in the given position is set + pub fn bit(&self, bit: u64) -> bool { + let bits_per_digit = u64::from(big_digit::BITS); + if let Some(digit_index) = (bit / bits_per_digit).to_usize() { + if let Some(digit) = self.data.get(digit_index) { + let bit_mask = (1 as BigDigit) << (bit % bits_per_digit); + return (digit & bit_mask) != 0; + } + } + false + } + + /// Sets or clears the bit in the given position + /// + /// Note that setting a bit greater than the current bit length, a reallocation may be needed + /// to store the new digits + pub fn set_bit(&mut self, bit: u64, value: bool) { + // Note: we're saturating `digit_index` and `new_len` -- any such case is guaranteed to + // fail allocation, and that's more consistent than adding our own overflow panics. + let bits_per_digit = u64::from(big_digit::BITS); + let digit_index = (bit / bits_per_digit) + .to_usize() + .unwrap_or(core::usize::MAX); + let bit_mask = (1 as BigDigit) << (bit % bits_per_digit); + if value { + if digit_index >= self.data.len() { + let new_len = digit_index.saturating_add(1); + self.data.resize(new_len, 0); + } + self.data[digit_index] |= bit_mask; + } else if digit_index < self.data.len() { + self.data[digit_index] &= !bit_mask; + // the top bit may have been cleared, so normalize + self.normalize(); + } + } } fn plain_modpow(base: &BigUint, exp_data: &[BigDigit], modulus: &BigUint) -> BigUint { diff --git a/tests/bigint.rs b/tests/bigint.rs index 292796ef..8eff5ba7 100644 --- a/tests/bigint.rs +++ b/tests/bigint.rs @@ -1307,3 +1307,96 @@ fn test_pow() { check!(u64); check!(usize); } + +#[test] +fn test_bit() { + // 12 = (1100)_2 + assert!(!BigInt::from(0b1100u8).bit(0)); + assert!(!BigInt::from(0b1100u8).bit(1)); + assert!(BigInt::from(0b1100u8).bit(2)); + assert!(BigInt::from(0b1100u8).bit(3)); + assert!(!BigInt::from(0b1100u8).bit(4)); + assert!(!BigInt::from(0b1100u8).bit(200)); + assert!(!BigInt::from(0b1100u8).bit(u64::MAX)); + // -12 = (...110100)_2 + assert!(!BigInt::from(-12i8).bit(0)); + assert!(!BigInt::from(-12i8).bit(1)); + assert!(BigInt::from(-12i8).bit(2)); + assert!(!BigInt::from(-12i8).bit(3)); + assert!(BigInt::from(-12i8).bit(4)); + assert!(BigInt::from(-12i8).bit(200)); + assert!(BigInt::from(-12i8).bit(u64::MAX)); +} + +#[test] +fn test_set_bit() { + let mut x: BigInt; + + // zero + x = BigInt::zero(); + x.set_bit(200, true); + assert_eq!(x, BigInt::one() << 200); + x = BigInt::zero(); + x.set_bit(200, false); + assert_eq!(x, BigInt::zero()); + + // positive numbers + x = BigInt::from_biguint(Plus, BigUint::one() << 200); + x.set_bit(10, true); + x.set_bit(200, false); + assert_eq!(x, BigInt::one() << 10); + x.set_bit(10, false); + x.set_bit(5, false); + assert_eq!(x, BigInt::zero()); + + // negative numbers + x = BigInt::from(-12i8); + x.set_bit(200, true); + assert_eq!(x, BigInt::from(-12i8)); + x.set_bit(200, false); + assert_eq!( + x, + BigInt::from_biguint(Minus, BigUint::from(12u8) | (BigUint::one() << 200)) + ); + x.set_bit(6, false); + assert_eq!( + x, + BigInt::from_biguint(Minus, BigUint::from(76u8) | (BigUint::one() << 200)) + ); + x.set_bit(6, true); + assert_eq!( + x, + BigInt::from_biguint(Minus, BigUint::from(12u8) | (BigUint::one() << 200)) + ); + x.set_bit(200, true); + assert_eq!(x, BigInt::from(-12i8)); + + x = BigInt::from_biguint(Minus, BigUint::one() << 30); + x.set_bit(10, true); + assert_eq!( + x, + BigInt::from_biguint(Minus, (BigUint::one() << 30) - (BigUint::one() << 10)) + ); + + x = BigInt::from_biguint(Minus, BigUint::one() << 200); + x.set_bit(40, true); + assert_eq!( + x, + BigInt::from_biguint(Minus, (BigUint::one() << 200) - (BigUint::one() << 40)) + ); + + x = BigInt::from_biguint(Minus, (BigUint::one() << 200) | (BigUint::one() << 100)); + x.set_bit(100, false); + assert_eq!( + x, + BigInt::from_biguint(Minus, (BigUint::one() << 200) | (BigUint::one() << 101)) + ); + + x = BigInt::from_biguint(Minus, (BigUint::one() << 63) | (BigUint::one() << 62)); + x.set_bit(62, false); + assert_eq!(x, BigInt::from_biguint(Minus, BigUint::one() << 64)); + + x = BigInt::from_biguint(Minus, (BigUint::one() << 200) - BigUint::one()); + x.set_bit(0, false); + assert_eq!(x, BigInt::from_biguint(Minus, BigUint::one() << 200)); +} diff --git a/tests/biguint.rs b/tests/biguint.rs index 3752c61d..13b69f21 100644 --- a/tests/biguint.rs +++ b/tests/biguint.rs @@ -1808,3 +1808,29 @@ fn test_count_ones() { let x: BigUint = (BigUint::from(3u8) << 128) | BigUint::from(3u8); assert_eq!(x.count_ones(), 4); } + +#[test] +fn test_bit() { + assert!(!BigUint::from(0u8).bit(0)); + assert!(!BigUint::from(0u8).bit(100)); + assert!(!BigUint::from(42u8).bit(4)); + assert!(BigUint::from(42u8).bit(5)); + let x: BigUint = (BigUint::from(3u8) << 128) | BigUint::from(3u8); + assert!(x.bit(129)); + assert!(!x.bit(130)); +} + +#[test] +fn test_set_bit() { + let mut x = BigUint::from(3u8); + x.set_bit(128, true); + x.set_bit(129, true); + assert_eq!(x, (BigUint::from(3u8) << 128) | BigUint::from(3u8)); + x.set_bit(0, false); + x.set_bit(128, false); + x.set_bit(130, false); + assert_eq!(x, (BigUint::from(2u8) << 128) | BigUint::from(2u8)); + x.set_bit(129, false); + x.set_bit(1, false); + assert_eq!(x, BigUint::zero()); +}