-
Notifications
You must be signed in to change notification settings - Fork 432
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
Port new Range implementation and only have one uniform float distribution #274
Conversation
9c8c4ad
to
d865501
Compare
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)
?
src/distributions/mod.rs
Outdated
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. |
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
@@ -87,12 +88,12 @@ mod impls { | |||
} | |||
} | |||
|
|||
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.
src/distributions/range.rs
Outdated
|
||
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
Range
implementation. Integers now use a much faster implementation based on a widening multiply instead of modulus.I have added a new private trait
IntoFloat
with aninto_float_with_exponent
method as a building block to convert from integers to floats.The
Open01
andClosed01
distributions are removed, andUniform
for floats will now return values in the open range (0, 1).IntoFloat
is also used in an optimised implementation inRange
, and inziggurat
.Will post benchmarks later today.