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

two's-complement logic operations on BigInt #26

Merged
merged 5 commits into from
Mar 6, 2018

Conversation

tspiteri
Copy link
Contributor

@tspiteri tspiteri commented Feb 27, 2018

This commit adds the following impls, giving results equivalent to what would be expected with two's complement.

  • impl Not for BigInt
  • impl BitAnd<&BigInt> for BigInt, impl BitAndAssign<&BigInt> for BigInt
  • impl BitOr<&BigInt> for BigInt, impl BitOrAssign<&BigInt> for BigInt
  • impl BitXor<&BigInt> for BigInt, impl BitXorAssign<&BigInt> for BigInt

I also added some tests. There is most probably some room for optimization, but the bitwise operations all operate in place using a single pass.

I did not add other implementations such as impl BitAnd<BigInt> for BigInt, those can be done later.

Edit: Fixes #13

Copy link
Member

@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

I just moved the tests around in #27, which causes a conflict here. I suggest making a standalone tests/bigint_bitwise.rs for your new tests.

I haven't looked too closely at the implementation details, but it seems pretty thorough AFAICT. Getting the tests absolutely right helps confidence here. Maybe some randomized i64 inputs would be a good idea for comparison too, in case there are edge cases you didn't think about.

N,
P(&'static [BigDigit]),
M(&'static [BigDigit]),
}
Copy link
Member

Choose a reason for hiding this comment

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

I suggest use V::*; for readability, to get rid of the V:: prefix everywhere else.

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);
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to also have these tests compared to the primitive operators, probably just with i64, at least for those where the values do fit. Just as a sanity check that your test setup is correct.

let a = BigInt::from(a);
let not = BigInt::from(not);
assert_eq!(!a.clone(), not, "!{:x}", a);
assert_eq!(!not.clone(), a, "!{:x}", not);
Copy link
Member

Choose a reason for hiding this comment

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

Here too, I think comparing to i64 is worthwhile.

src/bigint.rs Outdated
fn not(mut self) -> BigInt {
match self.sign {
Sign::NoSign => {
biguint::digits_mut(&mut self.data).push(1);
Copy link
Member

Choose a reason for hiding this comment

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

We support scalar arithmetic -- why not just self.data += 1?

src/bigint.rs Outdated
self.sign = Sign::Minus;
}
Sign::Minus => {
sub2(biguint::digits_mut(&mut self.data), &[1]);
Copy link
Member

Choose a reason for hiding this comment

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

self.data -= 1?

src/bigint.rs Outdated
let carry = __add2(biguint::digits_mut(&mut self.data), &[1]);
if carry != 0 {
biguint::digits_mut(&mut self.data).push(carry);
}
Copy link
Member

Choose a reason for hiding this comment

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

self.data += 1?

src/bigint.rs Outdated

// -1 ^ +ff = ...ffff ^ ...00ff = ...ff00 = +100
// -ff ^ +1 = ...ff01 ^ ...0001 = ...ff00 = +100
// answer is pos, has length of longest with a possible carry
Copy link
Member

Choose a reason for hiding this comment

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

answer is neg, but I think you got that right in practice.

@tspiteri
Copy link
Contributor Author

tspiteri commented Feb 27, 2018

I addressed your points in the first commit.

In the second commit I added tests on i64 values within ±3 of interesting values (i64::MIN, -u32::MAX, etc.). All logic operations are tested on all combinations with both i64 and BigInt, and the answers compared. That found a couple of bugs I fixed. I also added a few debug_assertions.

@tspiteri
Copy link
Contributor Author

I've added a third commit which makes the inline docs more helpful. I also made the use of the debug_assertions more consistent. I think this makes the code easier to understand.

Copy link
Member

@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

Some minor things can be simplified, then I'm happy. I'm impressed by how well the single abstraction of negate_carry makes this viable!

Let's not forget the val/ref combinations too, but that can be done later, as you say.

src/bigint.rs Outdated
self.assign_from_slice(Sign::NoSign, &[]);
return;
}
(Sign::Plus, Sign::Plus) => {
Copy link
Member

Choose a reason for hiding this comment

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

Can't this just use self.data &= other.data?

src/bigint.rs Outdated
self.assign_from_slice(other.sign, biguint::digits(&other.data));
return;
}
(Sign::Plus, Sign::Plus) => {
Copy link
Member

Choose a reason for hiding this comment

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

Could be self.data |= other.data.

src/bigint.rs Outdated
self.assign_from_slice(other.sign, biguint::digits(&other.data));
return;
}
(Sign::Plus, Sign::Plus) => {
Copy link
Member

Choose a reason for hiding this comment

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

Could be self.data ^= other.data.

src/biguint.rs Outdated
@@ -1686,6 +1686,24 @@ pub fn trailing_zeros(u: &BigUint) -> Option<usize> {
.map(|(i, digit)| i * big_digit::BITS + digit.trailing_zeros() as usize)
}

// needed since we cannot set visibility of `BigUint::data` to `pub(crate)`
Copy link
Member

Choose a reason for hiding this comment

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

I think we should just move struct BigUint to the crate root, and maybe its impl { fn normalize() } too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I understand this point. I have done something in the new commit 3 (crate-visible IntDigits) that maybe addresses this, but I'm not sure if that's what you had in mind. Anyway, I think further changes here can be independent of this PR.

Copy link
Member

Choose a reason for hiding this comment

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

I meant that if we moved the struct definition to the crate root, then everything could just access the data field directly. The digits_mut function especially was defeating any pretense of privacy within the crate anyway.

But now with your trait, I see how this is helpful to present a common interface for the macros, so this solution seems fine.

src/bigint.rs Outdated
fn not(mut self) -> BigInt {
match self.sign {
Sign::NoSign | Sign::Plus => {
self.data += 1 as BigDigit;
Copy link
Member

Choose a reason for hiding this comment

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

I guess you got tripped up by this defaulting to 1i32? Let's just use 1u32 instead of casting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You guessed right :)

@tspiteri tspiteri force-pushed the bitwise-bigint branch 2 times, most recently from 269ade7 to 57c3a0e Compare March 4, 2018 10:23
@tspiteri
Copy link
Contributor Author

tspiteri commented Mar 4, 2018

Commit 1 is now the implementation and commit 2 is the tests.

Commit 3 introduces a crate-visible IntDigits trait so that macros that used to use data.len() or data.capacity() can work for both BigUint and BigInt, and I also used it for BigInt to access BigUint::normalize. I made <BigUint as IntDigits>::normalize simply call the inherent method BigUint::normalize, maybe the implementation could move into the trait method and the inherent method can simply be removed?

Commit 4 implements ref/val forwarding for the bitwise ops of BigInt (depends on commit 3).

@cuviper
Copy link
Member

cuviper commented Mar 6, 2018

I took the small liberty of adding reference Not, but otherwise this quite comprehensive. Thanks a lot!

bors r+

bors bot added a commit that referenced this pull request Mar 6, 2018
26: two's-complement logic operations on BigInt r=cuviper a=tspiteri

This commit adds the following impls, giving results equivalent to what would be expected with two's complement.

* `impl Not for BigInt`
* `impl BitAnd<&BigInt> for BigInt`, `impl BitAndAssign<&BigInt> for BigInt`
* `impl BitOr<&BigInt> for BigInt`, `impl BitOrAssign<&BigInt> for BigInt`
* `impl BitXor<&BigInt> for BigInt`, `impl BitXorAssign<&BigInt> for BigInt`

I also added some tests. There is most probably some room for optimization, but the bitwise operations all operate in place using a single pass.

I did not add other implementations such as `impl BitAnd<BigInt> for BigInt`, those can be done later.

Edit: Fixes #13
@bors
Copy link
Contributor

bors bot commented Mar 6, 2018

Build succeeded

@bors bors bot merged commit ec0113e into rust-num:master Mar 6, 2018
@tspiteri tspiteri deleted the bitwise-bigint branch March 6, 2018 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants