- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 456
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
Deprecate Rng::gen_weighted_bool #308
Conversation
3b06ad0
to
b0a5e4f
Compare
I'd prefer to have |
b0a5e4f
to
95865cf
Compare
Implemented I went the easiest route here. Thanks for the suggestion to use my higher precision code here, that gives an excuse to add it 😄. But I'll hold of a little while longer for the range code, we would need to have some discussion first for how to expose it... |
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.
Some little doc issues but the code looks good.
src/lib.rs
Outdated
fn gen_weighted_bool(&mut self, n: u32) -> bool { | ||
// Short-circuit after `n <= 1` to avoid panic in `gen_range` | ||
n <= 1 || self.gen_range(0, n) == 0 | ||
} | ||
|
||
/// Return a bool with a `p` probability of being true. |
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.
Swap p
and probability
src/distributions/float.rs
Outdated
|
||
impl Distribution<$ty> for HighPrecision01 { | ||
/// Generate a floating point number in the open interval `(0, 1)` | ||
/// (not including either endpoint) with a uniform distribution. |
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 believe this is actually half-open (your old open version used rejection sampling; this code will yield 0 when the sampled u32 is zero). For sample < p
this is what we want anyway, so it's just the comment (and possibly the distribution name, I'm not sure on this yet) to change.
src/distributions/float.rs
Outdated
/// Generate a floating point number in the open interval `(0, 1)` | ||
/// (not including either endpoint) with a uniform distribution. | ||
/// | ||
/// This is different from `Uniform` in that it it uses all 32 bits |
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.
double 'it'
src/distributions/float.rs
Outdated
/// use rand::distributions::HighPrecision01; | ||
/// | ||
/// let val: f32 = SmallRng::new().sample(HighPrecision01); | ||
/// println!("f32 from (0,1): {}", val); |
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.
[0,1)
Maybe also document that the smallest non-zero value which can be generated is 0.00000000023283064 = 2.3e-10 (f32) or 0.00000000000000000005421010862427522 I see the distribution |
Thank you for the close read!
This is not really true, as the precision reduces when the number get higher. And it changes with every (negative) power of two. But I have tried to write some documentation with similar intent. |
Okay, I'm happy for this to be merged. I'd prefer you wait 2-3 days from first opening however in case anyone else has a concern. |
8cd8a5f
to
bd5ad92
Compare
Added a commit to use |
@dhardy I am getting second thoughts about this implementation of What are out limitations of
When can accuracy be problem when it is too little? I would say that is something very rare. And if you have an algorithm where the result depends on the accuracy of a function you are going to run for more then 2^52 times, I think it is your responsibility to glance over that function and determine if it fits your use. So an accuracy of 2^24 is sometimes too little. 2^52 is very more than enough. Could 2^32 be reasonable? That would allow us to use a single Now I think the following implementation can be interesting, because it turns into a comparison against a constant if fn gen_bool(&mut self, p: f64) -> bool {
assert!(p >= 0.0 && p <= 1.0);
let p_int = (p * core::u32::MAX as f64) as u32;
self.gen() < p_int
} |
That sounds reasonable (though it should be 1 in 2^32 bias is probably okay. I have been involved in experiments which may have used around 2^32 Bernoulli samples, but I doubt a single sample error would have had much effect on the results. I was wondering if the equivalent using |
Thank you, meant to write that... 😄 The equivalent with |
Changed When I tried to benchmark (does it really perform as hoped?) I had some trouble with
|
src/lib.rs
Outdated
@@ -551,7 +551,8 @@ pub trait Rng: RngCore { | |||
/// ``` | |||
fn gen_bool(&mut self, p: f64) -> bool { | |||
assert!(p >= 0.0 && p <= 1.0); | |||
self.sample::<f64, _>(distributions::HighPrecision01) < p | |||
let p_int = (p * core::u32::MAX as f64) as u32; | |||
p_int > self.gen() |
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 swap left-right sides and comparator now?
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.
Otherwise type inference couldn't figure it out... the alternative was self.gen::<u32>() <= p_int
.
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.
Oh, really? But you need to use >=
then.
@@ -864,7 +865,7 @@ impl SeedableRng for StdRng { | |||
} | |||
|
|||
/// An RNG recommended when small state, cheap initialization and good | |||
/// performance are required. The PRNG algorithm in `SmallRng` is choosen to be | |||
/// performance are required. The PRNG algorithm in `SmallRng` is chosen to be | |||
/// efficient on the current platform, **without consideration for cryptography |
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 that Xorshift has good next_u32
performance but not as good next_u64
performance as several other generators. Wait, something's wrong:
test gen_u32_xorshift ... bench: 4,635 ns/iter (+/- 198) = 862 MB/s
test gen_u64_xorshift ... bench: 2,840 ns/iter (+/- 93) = 2816 MB/s
Impossible that next_u64
is faster than next_u32
. Anyway, be careful comparing benchmarks for gen_bool
: I would imagine it most useful in heavy numerical simulators which would likely either use native 64-bit generators or buffered generators, i.e. a u32
may not be half the price of a u64
.
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.
True, sometimes it will be about half the price, sometimes it just makes no difference.
And you are getting bitten again (I think) by the rust bug with multiple codegen units and benchmarks harness. Can you retry with export RUSTFLAGS="-C codegen-units=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.
Interesting; didn't affect most tests (including xorshift bytes with around 900MB/s) but:
test gen_u32_xorshift ... bench: 1,045 ns/iter (+/- 59) = 3827 MB/s
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.
Actually, that was with another change calling black_box
less frequently. Without that I get approx 3000 MB/s. The u64
results only change by about 50MB/s however.
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 would also be nice if you cleaned this up and pulled the HighPrecision01
stuff into a separate PR, since it's not directly connected any more.
/// let mut rng = thread_rng(); | ||
/// println!("{}", rng.gen_bool(1.0 / 3.0)); | ||
/// ``` | ||
fn gen_bool(&mut self, p: f64) -> bool { |
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.
Please add a unit test, at the very least testing that gen_bool(1.0)
doesn't panic, or better testing that both 1.0 and 0.0 produce the expected outputs a few times over.
Can't I sneak in anything quietly? 😄 |
657f86e
to
caf811b
Compare
Rebased, removed the addition of I have added a test for |
The benchmark results have changed a bit, but finally realistic:
A floating point multiply is quite expensive compared to a couple of shifts and XORs, as it should be. Still when |
Looks good, apart from using |
Ah, the comment was collapsed.
|
No it's not. |
caf811b
to
a20c7b1
Compare
You are right. What was I confusing it with??? (updated) |
Ready to merge? |
Deprecate Rng::gen_weighted_bool
As discussed in #293.