Skip to content

Conversation

feadoor
Copy link
Contributor

@feadoor feadoor commented Jun 29, 2017

With my apologies for opening a new PR, and also for the 8 month delay, this continues the work started in #237 - the discussion there outlines the goals I was aiming for. I suppose this supersedes that PR and the other one can now be closed.

This PR adds support for Add, Sub, Mul, Div and Rem operations involving one BigInt/BigUint and one primitive integer, with operands in either order, and any combination of owned/borrowed arguments.

feadoor added 20 commits June 28, 2017 14:02
BigUint and BigInt can now be multiplied by a BigDigit, re-using the same buffer for the output, thereby reducing allocations and copying.
A BigDigit can be added to a BigUint - this is one of several
operations being implemented to allow scalar operations on BigInt and
BigUint across the board.
A BigDigit can be subtracted from a BigUint - this is one of several
operations being implemented to allow scalar operations on BigInt and
BigUint across the board.
A BigUint can be divided by a BigDigit - this is one of several
operations being implemented to allow scalar operations on BigInt and
BigUint across the board.
Allow the addition to occur with either operand order, and with any combination
of owned and borrowed arguments.
Allow the multiplication to occur with either operand order and with any
combination of owned and borrowed arguments.
Allow the subtraction to occur with either operand order and with any
combination of owned and borrowed arguments.
Allow the division to occur with either operand order and with any
combination of owned and borrowed arguments.
 - Don't apply attributes to statements (1.12.0)
 - Don't use checked_abs (1.13.0)
@cuviper
Copy link
Member

cuviper commented Jun 30, 2017

Great! I've been meaning to ping you about this. :)

Before I dig into a full review, I'm having second thoughts about doing this for all integer types, because of this one usability wart: type inference fails for unspecified literals.

error[E0277]: the trait bound `num::BigUint: std::ops::Add<i32>` is not satisfied                          
 --> examples/bigint.rs:6:13                                                                               
  |                                                                                                        
6 |     let x = BigUint::zero() + 42;                                                                      
  |             ^^^^^^^^^^^^^^^^^^^^ the trait `std::ops::Add<i32>` is not implemented for `num::BigUint`  
  |                                                                                                        
  = note: no implementation for `num::BigUint + i32`                                                       

(that example file isn't committed here, just something I wrote to try it out.)

When rustc can't infer the exact type for an integer literal, it just chooses i32, even though that's not one of the actual possibilities for BigUint. The options I see for us are:

  • accept that limitation; users will always have to write a specific unsigned literal type, e.g. 42u32.
  • go ahead and implement Add<i32> for BigUint, and treat negatives like overflow
    • maybe mapping to subtraction where possible, and map Sub<i32> negatives to addition.
  • step back and just implement u32 for BigUint.
    • BigInt can have both u32 and i32, as the inference fallback fits in the latter.

What do you think?

@feadoor
Copy link
Contributor Author

feadoor commented Jun 30, 2017

What a funny little error! Of your three options, I prefer the first. The compiler error is very clear about what's going on, and it doesn't seem unreasonable to be expected to add the type suffix in this case.

  • go ahead and implement Add<i32> for BigUint, and treat negatives like overflow
    • maybe mapping to subtraction where possible, and map Sub<i32> negatives to addition.

I don't like this option. Either i32 becomes a special case, which is confusing to users, or all signed types get implemented for BigUint, which encourages bad behaviour from users - signed primitives should belong with BigInt, and not with BigUint.

  • step back and just implement u32 for BigUint.
    • BigInt can have both u32 and i32, as the inference fallback fits in the latter.

I think it's more irritating / confusing for a user to think "Why can't I do this operation on a u64?" than it is for them to be told by the compiler that they need to specify the type of a literal.

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.

This is an impressive PR, thanks!

The few things I found are very minor. Unless you happen to be standing by right now, ready to make changes, I'll just go ahead myself.

@@ -299,6 +302,24 @@ impl Signed for BigInt {
}
}

// A convenience method for getting the absolute value of an i32 in a u32.
fn i32_abs_as_u32(a: i32) -> u32 {
Copy link
Member

Choose a reason for hiding this comment

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

This probably should be #[inline].

}

// A convenience method for getting the absolute value of an i64 in a u64.
fn i64_abs_as_u64(a: i64) -> u64 {
Copy link
Member

Choose a reason for hiding this comment

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

Also #[inline].

self.data.push(0);
}

let carry = __add2(&mut self.data, &[other]);
Copy link
Member

Choose a reason for hiding this comment

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

For len() == 0 && other == 0, won't this fail the __add2 length assertion?
(could just return self right away for other == 0, of course)

}

let (lo, hi) = big_digit::from_doublebigdigit(other);
let carry = __add2(&mut self.data, &[lo, hi]);
Copy link
Member

Choose a reason for hiding this comment

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

Similarly here, the __add2 length assertion doesn't care if they're zeros.

@cuviper
Copy link
Member

cuviper commented Jul 12, 2017

bors r+

bors bot added a commit that referenced this pull request Jul 12, 2017
313: Scalar operations across all integer types r=cuviper

With my apologies for opening a new PR, and also for the 8 month delay, this continues the work started in #237 - the discussion there outlines the goals I was aiming for. I suppose this supersedes that PR and the other one can now be closed.

This PR adds support for Add, Sub, Mul, Div and Rem operations involving one BigInt/BigUint and one primitive integer, with operands in either order, and any combination of owned/borrowed arguments.
@bors
Copy link
Contributor

bors bot commented Jul 12, 2017

Build succeeded

@bors bors bot merged commit e5434dc into rust-num:master Jul 12, 2017
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