-
Notifications
You must be signed in to change notification settings - Fork 2
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
Allow sampling from a closed integer range #2
Conversation
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.
Looks like most of the changes are to handle the case where unsigned_max is within the range, and yet it's still not possible to construct such a range.
I like the code but won't merge yet.
What do you think? We could add a Range::closed
or new_range_inclusive
constructor maybe?
src/distributions/range2.rs
Outdated
// the type, it has to store `unsigned_max + 1`, which can't be | ||
// represented. But a range of size 0 can't exist, and a | ||
// modulus op `unsigned_max + 1` is a no-op. So we treat this as | ||
// a special case. Wrapping arithmetic makes representing |
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.
Grammar: "even" before "makes", not "simple"
src/distributions/range2.rs
Outdated
// `unsigned_max + 1` as 0 even simple. | ||
// | ||
// We don't calculate zone directly, but first calculate the | ||
// number of integers to reject first. With a wrikle to handle |
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.
What's a "wrikle"?
I fixed the spelling in the comments, thank you. About how or where to add a constructor: there are so many designs and questions in the RFC thread, I don't know where to start. And I now almost nothing about API design... |
I'd rather address other stuff (the traits and crate split) on the RFC thread first; there's too much going on there at the moment. Using inclusive ranges seems sensible to me but would be a breaking change, so maybe a second constructor/function. |
Sorry for derailing the discussion on the RFC tread, I saw your comment to late. I sure don't think changing the default from open to closed ranges is a good idea! It would be very easy to silently break the code of others. Even with good documentation. |
I had two idea's that should help make the range code a bit faster in some cases:
I'll report back when I have something. |
d8facb2
to
fc0a6e5
Compare
I spend a few more hours trying to figure out why to code runs slower than it should. Without much success... It turns out the modulus operation is slow, but not the real problem. Apparently rust adds a check before dividing by 0, and possibly panics. This slowed down the function by about 5%. If I leave out the loop from The idea to pick a larger zone for u8 and u16 worked out nicely. But it got a little complicated with the rules for casting. The idea of providing two different techniques produced some nice and complicated code, but was not any faster. Benchmarks:
After:
I have added a function |
57b5c5b
to
272b385
Compare
Rebased. Before:
After:
Some improvements, some losses. But it all depends very much on the size of the range, at least for i32 and i65. It still think this pr is useful, as it adds support for closed ranges (e.g. handling ranges that can cover the entire range of the type). And the optimisation of small integers and extra comments. |
Yes, I had my head scratching why moving benchmarks from one module to another made a big difference, until I realised one used Can you add a |
272b385
to
a755d89
Compare
Finally finished this. Sorry for taking so long. Would it be okay if I make a PR that removes |
These changes make it possible to sample from closed ranges, not only from open. Included is a small optimisation for the modulus operator, and an optimisation for the types i8/u8 and i16/u16.
a755d89
to
d8b8474
Compare
No problem. Yes, I was planning on removing the original |
b15a99d
to
fdf5141
Compare
fdf5141
to
96503f7
Compare
Thank you. Removed the original |
@@ -263,6 +264,7 @@ pub use thread_local::{ThreadRng, thread_rng, set_thread_rng, set_new_thread_rng | |||
random, random_with}; | |||
|
|||
use prng::IsaacWordRng; | |||
use distributions::range::Range; |
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.
Note: there is a second import around line 472: use distributions::range::SampleRange;
.
I left it there to avoid a rebase.
Hi @dhardy. Is there a chance that you could merge this and the other PRs, or that we can move them along? |
Yeah, I guess. Sorry, I've been busy with some other work and travel the last couple of weeks, should have more time now. |
Thank you! |
This does not compile, but I wanted to get your opinion.
The last few days I tried just about every trick to make sampling from a range of integers faster. Like making the zone a power of 2 (so mod becomes &), and multiplication with mersenne numbers. In the end, you are just trading the modulus for more times to call the prng (18~25% more). Because prng's can be much slower than the Xorshift used in the benchmarks, this is not a promising route to take...
It also took me a lot of time to understand your method to calculate the zone. So I tried to come up with something myself, and it is exactly the same. I guess that proves there are no more off-by-one errors :-).
What I could do was add some extra comments to explain what is happening here. Also the modulus could be optimised a very little bit thanks to this trick: skipping it for the small chance
v
falls in the target range, making it a few percent faster.What I would like to know your opinion about: is it useful to expose an inclusive range? As per this comment. It fit a little better with my mental model when writing the code.