-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Conversation
Should be now ready for review. cc @expenses |
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.
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 { |
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.
Why does this consume other
instead of taking other: &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.
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 rhs
s by reference.
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.
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
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.
You don't need to resize if get
returns a default of 0 for out-of-range indices.
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.
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.
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 |
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 { |
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 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.
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.
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.
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 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.
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'd be preferable to choose the API in this PR and leave refactors in future PRs to just optimize implementation without changing API ideally.
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.
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.
@xlc see above:
|
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.
yes impressive clarity.
I haven't review mul and div yet though
/// 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 { |
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.
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
Anyone with an opinion on this^? |
We could also use BigNum, or BigUnsigned, or BigUint as in num-bigint crate 🤔 Maybe better: |
BigUint seems the most reasonable. |
API has been changed to Also refactored to |
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. |
muts are gone. I see the current state of the PR as a good fit to get in. |
/// - 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`. |
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 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
Co-Authored-By: thiolliere <[email protected]>
… into kiz-limb-arithmetic
/// 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] | ||
} |
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.
maybe I could be in favor of silently returning zero instead of panic but I'm also fine with that.
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.
Rational was to be act similar to a raw []
operator. I suppose with checked_get() it should be fine.
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 providetry_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:
Number
in multiply_by_rational.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: