Skip to content

Commit e108c47

Browse files
authored
Merge pull request #809 from dhardy/uniform-usize
Uniform distribution: bias and usize portability
2 parents d8a1254 + 0ac3766 commit e108c47

File tree

6 files changed

+160
-128
lines changed

6 files changed

+160
-128
lines changed

benches/distributions.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000));
149149
distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000));
150150
distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123));
151151
distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789));
152+
distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7));
153+
distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43));
154+
#[cfg(target_pointer_width = "64")]
155+
distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8));
156+
distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057));
152157

153158
distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319));
154159
distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319));

src/distributions/uniform.rs

Lines changed: 48 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,11 @@ pub trait UniformSampler: Sized {
246246
/// Sample a single value uniformly from a range with inclusive lower bound
247247
/// and exclusive upper bound `[low, high)`.
248248
///
249-
/// Usually users should not call this directly but instead use
250-
/// `Uniform::sample_single`, which asserts that `low < high` before calling
251-
/// this.
252-
///
253-
/// Via this method, implementations can provide a method optimized for
254-
/// sampling only a single value from the specified range. The default
255-
/// implementation simply calls `UniformSampler::new` then `sample` on the
256-
/// result.
249+
/// By default this is implemented using
250+
/// `UniformSampler::new(low, high).sample(rng)`. However, for some types
251+
/// more optimal implementations for single usage may be provided via this
252+
/// method (which is the case for integers and floats).
253+
/// Results may not be identical.
257254
fn sample_single<R: Rng + ?Sized, B1, B2>(low: B1, high: B2, rng: &mut R)
258255
-> Self::X
259256
where B1: SampleBorrow<Self::X> + Sized,
@@ -309,31 +306,29 @@ impl<'a, Borrowed> SampleBorrow<Borrowed> for &'a Borrowed where Borrowed: Sampl
309306
///
310307
/// # Implementation notes
311308
///
309+
/// For simplicity, we use the same generic struct `UniformInt<X>` for all
310+
/// integer types `X`. This gives us only one field type, `X`; to store unsigned
311+
/// values of this size, we take use the fact that these conversions are no-ops.
312+
///
312313
/// For a closed range, the number of possible numbers we should generate is
313-
/// `range = (high - low + 1)`. It is not possible to end up with a uniform
314-
/// distribution if we map *all* the random integers that can be generated to
315-
/// this range. We have to map integers from a `zone` that is a multiple of the
316-
/// range. The rest of the integers, that cause a bias, are rejected.
314+
/// `range = (high - low + 1)`. To avoid bias, we must ensure that the size of
315+
/// our sample space, `zone`, is a multiple of `range`; other values must be
316+
/// rejected (by replacing with a new random sample).
317317
///
318-
/// The problem with `range` is that to cover the full range of the type, it has
319-
/// to store `unsigned_max + 1`, which can't be represented. But if the range
320-
/// covers the full range of the type, no modulus is needed. A range of size 0
321-
/// can't exist, so we use that to represent this special case. Wrapping
322-
/// arithmetic even makes representing `unsigned_max + 1` as 0 simple.
318+
/// As a special case, we use `range = 0` to represent the full range of the
319+
/// result type (i.e. for `new_inclusive($ty::MIN, $ty::MAX)`).
323320
///
324-
/// We don't calculate `zone` directly, but first calculate the number of
325-
/// integers to reject. To handle `unsigned_max + 1` not fitting in the type,
326-
/// we use:
327-
/// `ints_to_reject = (unsigned_max + 1) % range;`
328-
/// `ints_to_reject = (unsigned_max - range + 1) % range;`
321+
/// The optimum `zone` is the largest product of `range` which fits in our
322+
/// (unsigned) target type. We calculate this by calculating how many numbers we
323+
/// must reject: `reject = (MAX + 1) % range = (MAX - range + 1) % range`. Any (large)
324+
/// product of `range` will suffice, thus in `sample_single` we multiply by a
325+
/// power of 2 via bit-shifting (faster but may cause more rejections).
329326
///
330-
/// The smallest integer PRNGs generate is `u32`. That is why for small integer
331-
/// sizes (`i8`/`u8` and `i16`/`u16`) there is an optimization: don't pick the
332-
/// largest zone that can fit in the small type, but pick the largest zone that
333-
/// can fit in an `u32`. `ints_to_reject` is always less than half the size of
334-
/// the small integer. This means the first bit of `zone` is always 1, and so
335-
/// are all the other preceding bits of a larger integer. The easiest way to
336-
/// grow the `zone` for the larger type is to simply sign extend it.
327+
/// The smallest integer PRNGs generate is `u32`. For 8- and 16-bit outputs we
328+
/// use `u32` for our `zone` and samples (because it's not slower and because
329+
/// it reduces the chance of having to reject a sample). In this case we cannot
330+
/// store `zone` in the target type since it is too large, however we know
331+
/// `ints_to_reject < range <= $unsigned::MAX`.
337332
///
338333
/// An alternative to using a modulus is widening multiply: After a widening
339334
/// multiply by `range`, the result is in the high word. Then comparing the low
@@ -342,12 +337,11 @@ impl<'a, Borrowed> SampleBorrow<Borrowed> for &'a Borrowed where Borrowed: Sampl
342337
pub struct UniformInt<X> {
343338
low: X,
344339
range: X,
345-
zone: X,
340+
z: X, // either ints_to_reject or zone depending on implementation
346341
}
347342

348343
macro_rules! uniform_int_impl {
349-
($ty:ty, $signed:ty, $unsigned:ident,
350-
$i_large:ident, $u_large:ident) => {
344+
($ty:ty, $unsigned:ident, $u_large:ident) => {
351345
impl SampleUniform for $ty {
352346
type Sampler = UniformInt<$ty>;
353347
}
@@ -382,34 +376,30 @@ macro_rules! uniform_int_impl {
382376
let high = *high_b.borrow();
383377
assert!(low <= high,
384378
"Uniform::new_inclusive called with `low > high`");
385-
let unsigned_max = ::core::$unsigned::MAX;
379+
let unsigned_max = ::core::$u_large::MAX;
386380

387381
let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned;
388382
let ints_to_reject =
389383
if range > 0 {
384+
let range = range as $u_large;
390385
(unsigned_max - range + 1) % range
391386
} else {
392387
0
393388
};
394-
let zone = unsigned_max - ints_to_reject;
395389

396390
UniformInt {
397391
low: low,
398392
// These are really $unsigned values, but store as $ty:
399393
range: range as $ty,
400-
zone: zone as $ty
394+
z: ints_to_reject as $unsigned as $ty
401395
}
402396
}
403397

404398
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
405399
let range = self.range as $unsigned as $u_large;
406400
if range > 0 {
407-
// Grow `zone` to fit a type of at least 32 bits, by
408-
// sign-extending it (the first bit is always 1, so are all
409-
// the preceding bits of the larger type).
410-
// For types that already have the right size, all the
411-
// casting is a no-op.
412-
let zone = self.zone as $signed as $i_large as $u_large;
401+
let unsigned_max = ::core::$u_large::MAX;
402+
let zone = unsigned_max - (self.z as $unsigned as $u_large);
413403
loop {
414404
let v: $u_large = rng.gen();
415405
let (hi, lo) = v.wmul(range);
@@ -431,7 +421,7 @@ macro_rules! uniform_int_impl {
431421
let low = *low_b.borrow();
432422
let high = *high_b.borrow();
433423
assert!(low < high,
434-
"Uniform::sample_single called with low >= high");
424+
"UniformSampler::sample_single: low >= high");
435425
let range = high.wrapping_sub(low) as $unsigned as $u_large;
436426
let zone =
437427
if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned {
@@ -459,20 +449,20 @@ macro_rules! uniform_int_impl {
459449
}
460450
}
461451

462-
uniform_int_impl! { i8, i8, u8, i32, u32 }
463-
uniform_int_impl! { i16, i16, u16, i32, u32 }
464-
uniform_int_impl! { i32, i32, u32, i32, u32 }
465-
uniform_int_impl! { i64, i64, u64, i64, u64 }
452+
uniform_int_impl! { i8, u8, u32 }
453+
uniform_int_impl! { i16, u16, u32 }
454+
uniform_int_impl! { i32, u32, u32 }
455+
uniform_int_impl! { i64, u64, u64 }
466456
#[cfg(all(rustc_1_26, not(target_os = "emscripten")))]
467-
uniform_int_impl! { i128, i128, u128, u128, u128 }
468-
uniform_int_impl! { isize, isize, usize, isize, usize }
469-
uniform_int_impl! { u8, i8, u8, i32, u32 }
470-
uniform_int_impl! { u16, i16, u16, i32, u32 }
471-
uniform_int_impl! { u32, i32, u32, i32, u32 }
472-
uniform_int_impl! { u64, i64, u64, i64, u64 }
473-
uniform_int_impl! { usize, isize, usize, isize, usize }
457+
uniform_int_impl! { i128, u128, u128 }
458+
uniform_int_impl! { isize, usize, usize }
459+
uniform_int_impl! { u8, u8, u32 }
460+
uniform_int_impl! { u16, u16, u32 }
461+
uniform_int_impl! { u32, u32, u32 }
462+
uniform_int_impl! { u64, u64, u64 }
463+
uniform_int_impl! { usize, usize, usize }
474464
#[cfg(all(rustc_1_26, not(target_os = "emscripten")))]
475-
uniform_int_impl! { u128, u128, u128, i128, u128 }
465+
uniform_int_impl! { u128, u128, u128 }
476466

477467
#[cfg(all(feature = "simd_support", feature = "nightly"))]
478468
macro_rules! uniform_simd_int_impl {
@@ -534,13 +524,13 @@ macro_rules! uniform_simd_int_impl {
534524
low: low,
535525
// These are really $unsigned values, but store as $ty:
536526
range: range.cast(),
537-
zone: zone.cast(),
527+
z: zone.cast(),
538528
}
539529
}
540530

541531
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
542532
let range: $unsigned = self.range.cast();
543-
let zone: $unsigned = self.zone.cast();
533+
let zone: $unsigned = self.z.cast();
544534

545535
// This might seem very slow, generating a whole new
546536
// SIMD vector for every sample rejection. For most uses
@@ -736,7 +726,7 @@ macro_rules! uniform_float_impl {
736726
let low = *low_b.borrow();
737727
let high = *high_b.borrow();
738728
assert!(low.all_lt(high),
739-
"Uniform::sample_single called with low >= high");
729+
"UniformSampler::sample_single: low >= high");
740730
let mut scale = high - low;
741731

742732
loop {
@@ -787,7 +777,7 @@ macro_rules! uniform_float_impl {
787777
let mask = !scale.finite_mask();
788778
if mask.any() {
789779
assert!(low.all_finite() && high.all_finite(),
790-
"Uniform::sample_single called with non-finite boundaries");
780+
"Uniform::sample_single: low and high must be finite");
791781
scale = scale.decrease_masked(mask);
792782
}
793783
}

0 commit comments

Comments
 (0)