@@ -117,29 +117,49 @@ macro_rules! range_int_impl {
117117 }
118118
119119 impl RangeImpl for RangeInt <$ty> {
120- // we play free and fast with unsigned vs signed here
120+ // We play free and fast with unsigned vs signed here
121121 // (when $ty is signed), but that's fine, since the
122122 // contract of this macro is for $ty and $unsigned to be
123- // "bit-equal", so casting between them is a no-op & a
124- // bijection.
123+ // "bit-equal", so casting between them is a no-op.
125124
126125 type X = $ty;
127126
128127 fn new( low: Self :: X , high: Self :: X ) -> Self {
129- let range = ( w( high as $unsigned) - w( low as $unsigned) ) . 0 ;
128+ new_closed( low, high - 1 )
129+ }
130+
131+ fn new_closed( low: Self :: X , high: Self :: X ) -> Self {
132+ // For a closed range the number of possible numbers we should
133+ // generate is `range = (high - low + 1)`. It is not possible to
134+ // end up with a uniform distribution if we map _all_ the random
135+ // integers that can be generated to this range. We have to map
136+ // integers from a `zone` that is a multiple of the range. The
137+ // rest of the integers, that cause a bias, are rejected. The
138+ // sampled number is `zone % range`.
139+ //
140+ // The problem with `range` is that to cover the full range of
141+ // the type, it has to store `unsigned_max + 1`, which can't be
142+ // represented. But a range of size 0 can't exist, and a
143+ // modulus op `unsigned_max + 1` is a no-op. So we treat this as
144+ // a special case. Wrapping arithmetic makes representing
145+ // `unsigned_max + 1` as 0 even simple.
146+ //
147+ // We don't calculate zone directly, but first calculate the
148+ // number of integers to reject first. With a wrikle to handle
149+ // `unsigned_max + 1` not fitting in the type, this is:
150+ // ints_to_reject = (unsigned_max + 1) % range;
151+ // ints_to_reject = (unsigned_max - range + 1) % range;
152+
130153 let unsigned_max: $unsigned = :: core:: $unsigned:: MAX ;
131154
132- // We want to calculate type_range % range where type_range is
133- // pow(2, n_bits($ty)), but we can't represent type_range.
134- // (type_range - range) % range is equivalent, since we know
135- // type_range > range. Since range >= 1,
136- // type_range - range = (unsigned_max - range) + 1.
137- let ignore_zone = ( ( unsigned_max - range) + 1 ) % range;
138- // We want to sample from the zone
139- // [0, (type_range - ignore_zone))
140- // however, ignore_zone may be zero. Instead use a closed range
141- // from zero to:
142- let zone = unsigned_max - ignore_zone;
155+ let range = ( w( high as $unsigned) - w( low as $unsigned) ) . 0 ;
156+ let ints_to_reject =
157+ if range > 0 {
158+ ( unsigned_max - range + 1 ) % range
159+ } else {
160+ 0
161+ } ;
162+ let zone = unsigned_max - ints_to_reject;
143163
144164 RangeInt {
145165 low: low,
@@ -148,16 +168,21 @@ macro_rules! range_int_impl {
148168 zone: zone as $ty
149169 }
150170 }
151-
171+
152172 fn sample<R : Rng +?Sized >( & self , rng: & mut R ) -> Self :: X {
153173 use $crate:: distributions:: uniform;
174+ let range = self . range as $unsigned;
154175 loop {
155176 let v: $unsigned = uniform( rng) ;
156- // Reject samples not between 0 and zone:
157- if v <= self . zone as $unsigned {
158- // Adjustment sample for range and low value:
159- return ( w( self . low) + w( ( v % self . range as $unsigned) as $ty) ) . 0 ;
177+ // The modulus operator is incredibly slow. Even skipping
178+ // it for the small chance `v` falls in the target range
179+ // makes it a few percent faster.
180+ if v <= range || self . range == 0 {
181+ return ( w( self . low) + w( v as $ty) ) . 0 ;
182+ } else if v <= self . zone as $unsigned {
183+ return ( w( self . low) + w( ( v % range) as $ty) ) . 0 ;
160184 }
185+ // Sample does not fall in `zone`, so reject it and retry.
161186 }
162187 }
163188 }
0 commit comments