-
-
Notifications
You must be signed in to change notification settings - Fork 481
Port new Range implementation and only have one uniform float distribution #274
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
Conversation
9c8c4ad to
d865501
Compare
dhardy
left a comment
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.
Doesn't this leave next_f32/64 hanging around uselessly? You might as well remove those, and I'll remove that part of my PR.
I still need to take a closer look, but it's great to finally have this bit land!
@tspiteri are you interested in reviewing this?
src/distributions/float.rs
Outdated
| /// 52 for `f64`. | ||
| /// The resulting value will fall in a range that depends on the exponent. | ||
| /// As an example the range with exponent 0 will be | ||
| /// [2<sup>0</sup>..2<sup>1</sup>-1), which is [1..2). |
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.
2.pow(1) - 1 = 1, not 2 — or am I not reading it right?
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.
Oops, -1 is not supposed to be there
src/distributions/mod.rs
Outdated
| // TODO: This range is not open, is that a poblem? | ||
| (bits >> 12).into_float_with_exponent(1) - 3.0 | ||
| } else { | ||
| // Convert to a value in the range [0,1) and substract to get (0,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.
range [1, 2) ?
| let u = if symmetric { | ||
| // Convert to a value in the range [2,4) and substract to get [-1,1) | ||
| // TODO: This range is not open, is that a poblem? | ||
| (bits >> 12).into_float_with_exponent(1) - 3.0 |
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.
Don't know. But is it not easy to make it open?
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 can't be done by changing the constant 3.0, because 3.0-EPSILON is not representable. So we would need one extra addition. But I looked a bit more careful at the function(s) and can't imagine it to be a problem.
d865501 to
a4cad05
Compare
|
Benchmarks, taken with the following commands using Most of the distributions are a little faster with the optimised float conversion, and the others thanks to the new range code. |
👍 |
|
Travis has some problem with incremental compilation, but closing and reopening the PR does not make it retry. No problem |
|
Added two tiny commits. One to use I vaguely remember that generating bools also became faster, but apparently not... |
|
One thing I think you may have missed: I don't think |
0f3d818 to
1b916d5
Compare
The previous code would reject about 50% of the generated numbers, because chars are always lower than `0x11_0000`, half of the masked `0x1f_ffff`.
1b916d5 to
4a9c465
Compare
|
Rebased after the merge of #273.
Comparing against zero should be just a bit faster than doing a mask first, but I remembered wrong. |
dhardy
left a comment
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.
Wow; that's a lot to review! I see a lot of the range code is unchanged from my master branch but that you simplified float sampling; I guess this makes sense with reduced precision.
src/distributions/float.rs
Outdated
| #[inline(always)] | ||
| fn into_float_with_exponent(self, exponent: i32) -> $ty { | ||
| // The exponent is encoded using an offset-binary representation, | ||
| // with the zero offset being 127 |
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.
127 is only correct for f32 I think? Maybe reduce this doc.
|
|
||
| let value = rng.$next_u(); | ||
| let fraction = value >> (float_size - $fraction_bits); | ||
| fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0) |
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.
If 1+ε is the smallest representable number above 1, then 1-ε/2 is representable; ok. This is the same adjustment as the Open01 removed here but in a single number. Looks fine and functionally identical.
src/distributions/float.rs
Outdated
| fn into_float_with_exponent(self, exponent: i32) -> $ty { | ||
| // The exponent is encoded using an offset-binary representation, | ||
| // with the zero offset being 127 | ||
| let exponent_bits = (($exponent_bias + exponent) as $uty) << $fraction_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.
Equivalent to removed UPPER_MASK when exponent == 0; ok.
src/distributions/mod.rs
Outdated
| } | ||
|
|
||
| impl<Sup: SampleRange> Sample<Sup> for Range<Sup> { | ||
| impl<Sup: SampleRange + RangeImpl<X = Sup>> Sample<Sup> for Range<Sup> { |
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 think this is wrong and won't actually generate any implementations — I think it should be impl<T: RangeImpl> Sample<T::X> for Range<T>. Also below.
src/distributions/mod.rs
Outdated
| /// [`StandardNormal`] distributions produce floating point numbers with | ||
| /// alternative ranges or distributions.) | ||
| /// open range `(0, 1)`. (The [`Exp1`], and [`StandardNormal`] distributions | ||
| /// produce floating point numbers with alternative ranges or distributions.) |
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 last sentence is off-topic now; I think just remove it.
|
|
||
| macro_rules! range_int_impl { | ||
| ($ty:ty, $signed:ident, $unsigned:ident, | ||
| $i_large:ident, $u_large:ident) => { |
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.
All types should be ty, not ident.
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.
The names are also used like ::core::$u_large::MAX. ident works for both, but ty only for types.
src/distributions/range.rs
Outdated
| } | ||
|
|
||
| macro_rules! wmul_impl { | ||
| ($ty:ty, $wide:ident, $shift:expr) => { |
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.
$wide:ty
| fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X { | ||
| // Generate a value in the range [1, 2) | ||
| let value1_2 = (rng.$next_u() >> $bits_to_discard) | ||
| .into_float_with_exponent(0); |
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.
So this range is half-open, unlike Uniform. Slightly odd, but okay I guess.
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, I was not perfectly happy about the difference. But I haven't yet thought through all the problematic rounding cases. We should not make any guarantees about whether the ranges are open or closed yet.
src/distributions/range.rs
Outdated
| use distributions::range::{Range, RangeImpl, RangeFloat, SampleRange}; | ||
|
|
||
| #[test] | ||
| fn test_fn_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.
This test is pretty strange: why two separate loops? why not cache the ranges? do we also test the single-sample variant, and for various types?
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.
Oops, I copied the tests but didn't look closely. You are right, the first three test do not make much sense or are duplicates.
src/distributions/range.rs
Outdated
| #[should_panic] | ||
| fn test_fn_range_panic_usize() { | ||
| let mut r = ::test::rng(816); | ||
| Range::new(5, 2).sample(&mut r); |
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.
Doesn't use usize like name implies. This and the previous fn are redundant. Maybe add one unit-test to test all supported int types?
|
Thanks for the careful read! |
src/distributions/range.rs
Outdated
| /// it is itself uniform and the `RangeImpl` implementation is correct). | ||
| /// `Range::new` and `Range::new_inclusive` will set up a `Range`, which does | ||
| /// some preparations up front to make sampeling values faster. | ||
| /// `Range::sample_single` is optimized for sampeling values once or only a |
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.
No 'e' in 'sampling' (also below), but 👍
Finally finished the first part of this.
The biggest change is the porting the new
Rangeimplementation. Integers now use a much faster implementation based on a widening multiply instead of modulus.I have added a new private trait
IntoFloatwith aninto_float_with_exponentmethod as a building block to convert from integers to floats.The
Open01andClosed01distributions are removed, andUniformfor floats will now return values in the open range (0, 1).IntoFloatis also used in an optimised implementation inRange, and inziggurat.Will post benchmarks later today.