Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Multi-limb arithmetic for runtime #3743

Merged
merged 22 commits into from
Oct 10, 2019
Merged

Multi-limb arithmetic for runtime #3743

merged 22 commits into from
Oct 10, 2019

Conversation

kianenigma
Copy link
Contributor

@kianenigma kianenigma commented Oct 2, 2019

Closes #3670

This adds a new minimal type, Number to the arithmetic primitive types of the runtime that can be used to store potentially infinitely large unsigned (or not #3716) integers and safely apply the basic operations on them. Number can be built from all numeric types and provide try_into all typical numeric types.

The main objective was to support an always accurate
pub fn multiply_by_rational(a: u128, b: u128, c: u128) -> Result<u128, &'static str> while this PR brings more. The main reason is that to have a multiply and division you need both the addition and subtraction for large numbers as well. And the abstraction added is the minimum needed to have a reasonably readable code. The only --potential-- downside is that maybe a super-ugly code that was specialised for only two limbs of u128 would have been slightly faster due to loop unrolling etc, but the algorithms would have been the same.

before it can properly be reviewed:

  • deal with the few remaining TODOs
  • actually use Number in multiply_by_rational.
  • decide if Number is even a good name 😕

The code is already big I so I will probably not do it here but if this gets merged, there should be follow-up PRs to:

  • relocate all the arithmetic stuff into a separate crate.
  • create better and more fuzzing code for all tests that bombard the code with random values.

@kianenigma kianenigma marked this pull request as ready for review October 7, 2019 14:39
@kianenigma kianenigma added the A0-please_review Pull request needs code review. label Oct 7, 2019
@kianenigma
Copy link
Contributor Author

Should be now ready for review.

cc @expenses

Copy link
Contributor

@jimpo jimpo left a comment

Choose a reason for hiding this comment

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

Haven't reviewed division or tests yet. Generally very nicely commented IMO.

There seems to be lots of room for memory efficiency improvements. I think switching to little-endian order (least significant limbs at the front of the vec) would make a lot of stuff easier.

/// limbs. The caller may strip the output if desired.
///
/// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4.
pub fn add(mut self, mut other: Self) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this consume other instead of taking other: &Self?

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 am generally more in favour of consuming when an arithmetic operation cannot fail. If it might fail, then it is more sensible to take a reference.

But now seeing the big picture here, I suppose I could also take all the rhss by reference.

Copy link
Contributor

Choose a reason for hiding this comment

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

currently the code does resize input and so modify them. maybe we can actually implement resize in another way taking only references but otherwise taking ownership seems to make sense to me here

Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to resize if get returns a default of 0 for out-of-range indices.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

resize cannot change (it must change the caller somehow) and get should be a raw getter preferably, similar to other std types. We can have a checked_get() -> Option<_> and unwrap_or over it.

I am quite doubtful still regarding accepting arguments as ref or owning.
For the reason mentioned above, I prefer owning. But since this type can own a large vector of numbers moving stuff around is for sure non-sensical. Moreover, this type is still a number and the std implementation of those takes everything by value (thou' this is not a strong argument as those all have Copy).

Hence, if we are to value speed over consistency, I don't see it fair to only make other a ref and still consume self. Both will be ref. I am in favour of implementing this.

core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
core/sr-primitives/src/sr_arithmetic.rs Show resolved Hide resolved
core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
@kianenigma
Copy link
Contributor Author

kianenigma commented Oct 7, 2019

There seems to be lots of room for memory efficiency improvements. I think switching to little-endian order (least significant limbs at the front of the vec) would make a lot of stuff easier.

Oh big time. but I intentionally did not (and rather not) include it in this initial PR in favour of brevity. But would like to hear more about your opinion on it.

EDIT: One of the resources that I had for optimisation but have iced for now, and is an interesting read https://skanthak.homepage.t-online.de/division.html

@xlc
Copy link
Contributor

xlc commented Oct 7, 2019

Will it better to move this into a standalone crate? So that other non-substrate projects (smart contracts) can use this as well

}
}

impl ops::Add for Number {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these traits need to be implemented? I think ideally you want to encourage adds, muls etc. that use references instead of taking ownership. Otherwise looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I was being too biased here with my personal preference of taking ownership when an operation always returns. I will make all the rhs's reference.

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 have to see, maybe we can also not do it here and leave it for some benchmarks to clearly show the difference and then refactor to reference in some future PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be preferable to choose the API in this PR and leave refactors in future PRs to just optimize implementation without changing API ideally.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes but anyway this is not a big deal. Number implements Clone, changing the API doesn't really break code, user just have to make some more clone and borrow in part of its code that's all.

@kianenigma
Copy link
Contributor Author

Will it better to move this into a standalone crate? So that other non-substrate projects (smart contracts) can use this as well

@xlc see above:

The code is already big I so I will probably not do it here but if this gets merged, there should be follow-up PRs to:

relocate all the arithmetic stuff into a separate crate.
create better and more fuzzing code for all tests that bombard the code with random values.

Copy link
Contributor

@gui1117 gui1117 left a comment

Choose a reason for hiding this comment

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

yes impressive clarity.
I haven't review mul and div yet though

core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
/// limbs. The caller may strip the output if desired.
///
/// Taken from "The Art of Computer Programming" by D.E. Knuth, vol 2, chapter 4.
pub fn add(mut self, mut other: Self) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

currently the code does resize input and so modify them. maybe we can actually implement resize in another way taking only references but otherwise taking ownership seems to make sense to me here

core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
@kianenigma
Copy link
Contributor Author

decide if Number is even a good name 😕

Anyone with an opinion on this^?

@gui1117
Copy link
Contributor

gui1117 commented Oct 8, 2019

decide if Number is even a good name confused

Anyone with an opinion on this^?

We could also use BigNum, or BigUnsigned, or BigUint as in num-bigint crate 🤔

Maybe better:
UsignedMultiLimb, UMultiLimb.

@kianenigma
Copy link
Contributor Author

decide if Number is even a good name confused

Anyone with an opinion on this^?

We could also use BigNum, or BigUnsigned, or BigUint as in num-bigint crate 🤔

BigUint seems the most reasonable.

@kianenigma
Copy link
Contributor Author

kianenigma commented Oct 9, 2019

API has been changed to (self, other: &mut Self) in all cases for now. I might still try and remove the mutability requirement by eliminating resize, but other than that I won't touch anything else. Lots of improvements to made listed all here #3745

Also refactored to BigUint

@gui1117
Copy link
Contributor

gui1117 commented Oct 9, 2019

taking mutable reference seems a bit weird to me, let's say you only have a immutable value you will have to clone it to add it to another one.

Maybe you could remove the resize by introducing a function iterating_resized which only takes reference to BigUInt and iterate on its digits as if it was resized.

@kianenigma
Copy link
Contributor Author

taking mutable reference seems a bit weird to me, let's say you only have a immutable value you will have to clone it to add it to another one.

muts are gone. I see the current state of the PR as a good fit to get in.

@kianenigma kianenigma requested a review from gui1117 October 9, 2019 13:10
core/sr-primitives/src/sr_arithmetic.rs Show resolved Hide resolved
Comment on lines 965 to 968
/// - requires `other` to be stripped and have no leading zeros.
/// - requires `self` to be stripped and have no leading zeros.
/// - requires `other` to have at least two limbs.
/// - requires `self` to have a greater or equal length compared to `other`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel those constrains are quite strong maybe this could be reduced.

Actually I wonder if we want user to use this BigUInt directly maybe the most exposed API should be with less contrained as require and returning stripped output always ?

like we would rename this div_stripped_multiple_limbs and expose a div which only constrain is that other is different from 0 otherwise it returns 0

core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
core/sr-primitives/src/sr_arithmetic.rs Outdated Show resolved Hide resolved
Comment on lines +758 to +765
/// A naive getter for limb at `index`. Note that the order is lsb -> msb.
///
/// #### Panics
///
/// This panics if index is out of range.
pub fn get(&self, index: usize) -> Single {
self.digits[self.len() - 1 - index]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe I could be in favor of silently returning zero instead of panic but I'm also fine with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rational was to be act similar to a raw [] operator. I suppose with checked_get() it should be fine.

@kianenigma kianenigma merged commit 66042f1 into master Oct 10, 2019
@kianenigma kianenigma deleted the kiz-limb-arithmetic branch October 10, 2019 07:52
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Accurately compute operations on u128
6 participants