Skip to content

Commit

Permalink
Merge #183
Browse files Browse the repository at this point in the history
183: Get/set n-th bit of `BigUint` and `BigInt` r=cuviper a=janmarthedal

This PR implements `bit` and `set_bit` for `BigUint` and `BigInt`.

The method names have been chosen to match those of Ramp (https://docs.rs/ramp/0.5.9/ramp/int/struct.Int.html#method.bit).

For `BigInt` the implementation uses the number's two's complement representation when the number is negative. This matches what libraries like Ramp or languages like Python do.

Resolves #172 

Co-authored-by: Jan Marthedal Rasmussen <jan@janmr.com>
Co-authored-by: Josh Stone <cuviper@gmail.com>
  • Loading branch information
3 people committed Feb 24, 2021
2 parents 1368a42 + 69f654e commit 7492eec
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 1 deletion.
5 changes: 4 additions & 1 deletion ci/big_quickcheck/Cargo.toml
Expand Up @@ -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 = "../.."
123 changes: 123 additions & 0 deletions src/bigint.rs
Expand Up @@ -3254,6 +3254,129 @@ impl BigInt {
pub fn trailing_zeros(&self) -> Option<u64> {
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);
Expand Down
37 changes: 37 additions & 0 deletions src/biguint.rs
Expand Up @@ -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 {
Expand Down
93 changes: 93 additions & 0 deletions tests/bigint.rs
Expand Up @@ -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));
}
26 changes: 26 additions & 0 deletions tests/biguint.rs
Expand Up @@ -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());
}

0 comments on commit 7492eec

Please sign in to comment.