-
Notifications
You must be signed in to change notification settings - Fork 54
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
Int
- two's complement
#695
base: signed-int
Are you sure you want to change the base?
Conversation
FYI: local benchmark results @ b23483d
|
src/int.rs
Outdated
/// | ||
/// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. | ||
#[inline] | ||
fn negate_unsafe(&self) -> (Self, Choice) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it does what is the same as self.0.wrapping_neg()
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out! I adapted wrapping_neg()
into wrapping_negc()
to include the carry and still get the is_zero
for free. In addition to having no duplicate code, this saves anywhere from 8% (I128
) to 1% (I4096
) in the multiplication benchmark 👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw. why do you need is_zero in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I needed it for mul
and div
; the fix you pointed out there makes knowing whether something is zero unnecessary (for now).
|
Thanks @tarcieri |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make it into the feature branch so we have a baseline to work on.
LGTM to initial commit.
Will do another pass tomorrow, but I agree this is already a lot |
// TODO(tarcieri): replace with `const impl From<i64>` when stable | ||
#[cfg(target_pointer_width = "32")] | ||
pub const fn from_i64(n: i64) -> Self { | ||
Int::new_from_uint(Uint::<{ I64::LIMBS }>::from_u64(n as u64)).resize() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should work and is a little simpler:
Int::new_from_uint(Uint::from_u64(n as u 64))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that would work. Let me explain why:
In scaling up a negative integer, we have to make sure that all new limbs are initialized with Limb::MAX
instead of Limb::ZERO
(as is the default for Uint
). So, what I'm doing here is:
- cast
i64
tou64
, - ingest this
u64
into aUint
that is as small as possible, - wrap the
Uint
into anInt
, and - scale up the
int
(which I wrote to make sure it uses the rightLimb
type).
IIUC, Int::new_from_uint(Uint::from_u64(n as u 64))
does
- cast
i64
tou64
, - ingest this
u64
into aUint<LIMBS>
, filling any missing limbs withLimb::ZERO
, and - wrapping the
Uint
into anInt
.
This would not work for negative values.
Would you agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh, right,
I forgot that you can call from_i64 to Int with more than two limbs :)
@tarcieri @erik-3milabs |
/// I128::from(2) | ||
/// ) | ||
/// ``` | ||
pub fn checked_div_floor(&self, rhs: &Self) -> CtOption<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this method?
I think one method that rounds towards zero it sufficient (so it works like regular primitive int division).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need it? No, probably not. Do I need it? Yes, yes I do :)
I need this operation as well as div_mod_floor
for a project I'm working on (that op is one of the reasons I'm implementing this right now 🙈). IIUC, the definition I am using here aligns with num_bigint::BigInt::div_mod_floor
, which I'm trying to replace to make my algorithm constant time.
This function, of course, differs slightly from Int::checked_div
in that it rounds down, instead of to zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@erik-3milabs out of curiosity, if this is not a secret, what do you need the floor_div
for?
@lleoha the wrappers idea sounds possibly good (although I'm unclear on use cases), however something probably best tackled after getting this PR over the finish line |
src/int/div.rs
Outdated
/// Base checked_div_mod operation. | ||
/// Given `(a, b)` computes the quotient and remainder of the absolute | ||
/// Note: this operation rounds towards zero, truncating any fractional part of the exact result. | ||
fn checked_div_mod( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we instead have a method that is consistent with primitive div/rem i.e. return quotient and remainder with quotient round towards zero and both q and r being signed integer as they both can be negative e.g. -5 / 2 = (-2, -1).
Also to be consistent with Uint can we rename it to div_rem instead of div_mod?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was a little chaotic,
What I'm trying to say is that returning two Uints and one bit is not enough to cover all the cases:
-5/-2 = (2, -1)
-5/2 = (-2, -1)
5/-2 = (-2, 1)
5/2 = (2, 1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, didn't think of that. Let me fix this later today!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the code. Let me know what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Failing CI should be fixed by erik-3milabs#1 which is slated to be merged. |
Add widening_mul, group absolute value operations, reformat.
Ok, this PR has gotten out of hand... Oops... With all checks passing, I'm proposing to freeze it here; I'll create a new PR for new features I intend to introduce. |
@tarcieri, could you please re-review this and let me know what still needs to be done to merge this? |
Topic
Adding
Int
support (see #624)To decide on sign-and-magnitude (#694) vs. two's complement, I whipped up a quick two's complement implementation of the basic add/sub/mul/div operations. It's not perfect yet; I'm just curious to see how it compares to #694.