3939
4040"""
4141
42+ from warnings import warn as _warn
43+ from types import MethodType as _MethodType , BuiltinMethodType as _BuiltinMethodType
4244from math import log as _log , exp as _exp , pi as _pi , e as _e
4345from math import sqrt as _sqrt , acos as _acos , cos as _cos , sin as _sin
4446from math import floor as _floor
4749 "randrange" ,"shuffle" ,"normalvariate" ,"lognormvariate" ,
4850 "expovariate" ,"vonmisesvariate" ,"gammavariate" ,
4951 "gauss" ,"betavariate" ,"paretovariate" ,"weibullvariate" ,
50- "getstate" ,"setstate" ,"jumpahead" ]
52+ "getstate" ,"setstate" ,"jumpahead" , "WichmannHill" , "getrandbits" ,
53+ "Random" ]
5154
5255NV_MAGICCONST = 4 * _exp (- 0.5 )/ _sqrt (2.0 )
5356TWOPI = 2.0 * _pi
5457LOG4 = _log (4.0 )
5558SG_MAGICCONST = 1.0 + _log (4.5 )
59+ BPF = 53 # Number of bits in a float
5660
5761# Translated by Guido van Rossum from C source provided by
5862# Adrian Baddeley. Adapted by Raymond Hettinger for use with
@@ -72,6 +76,8 @@ class Random(_random.Random):
7276 Class Random can also be subclassed if you want to use a different basic
7377 generator of your own devising: in that case, override the following
7478 methods: random(), seed(), getstate(), setstate() and jumpahead().
79+ Optionally, implement a getrandombits() method so that randrange()
80+ can cover arbitrarily large ranges.
7581
7682 """
7783
@@ -131,12 +137,13 @@ def __reduce__(self):
131137
132138## -------------------- integer methods -------------------
133139
134- def randrange (self , start , stop = None , step = 1 , int = int , default = None ):
140+ def randrange (self , start , stop = None , step = 1 , int = int , default = None ,
141+ maxwidth = 1L << BPF ):
135142 """Choose a random item from range(start, stop[, step]).
136143
137144 This fixes the problem with randint() which includes the
138145 endpoint; in Python this is usually not what you want.
139- Do not supply the 'int' and 'default ' arguments.
146+ Do not supply the 'int', 'default', and 'maxwidth ' arguments.
140147 """
141148
142149 # This code is a bit messy to make it fast for the
@@ -146,43 +153,52 @@ def randrange(self, start, stop=None, step=1, int=int, default=None):
146153 raise ValueError , "non-integer arg 1 for randrange()"
147154 if stop is default :
148155 if istart > 0 :
156+ if istart >= maxwidth :
157+ return self ._randbelow (istart )
149158 return int (self .random () * istart )
150159 raise ValueError , "empty range for randrange()"
151160
152161 # stop argument supplied.
153162 istop = int (stop )
154163 if istop != stop :
155164 raise ValueError , "non-integer stop for randrange()"
156- if step == 1 and istart < istop :
165+ width = istop - istart
166+ if step == 1 and width > 0 :
157167 # Note that
158- # int(istart + self.random()*(istop - istart) )
168+ # int(istart + self.random()*width )
159169 # instead would be incorrect. For example, consider istart
160170 # = -2 and istop = 0. Then the guts would be in
161171 # -2.0 to 0.0 exclusive on both ends (ignoring that random()
162172 # might return 0.0), and because int() truncates toward 0, the
163173 # final result would be -1 or 0 (instead of -2 or -1).
164- # istart + int(self.random()*(istop - istart) )
174+ # istart + int(self.random()*width )
165175 # would also be incorrect, for a subtler reason: the RHS
166176 # can return a long, and then randrange() would also return
167177 # a long, but we're supposed to return an int (for backward
168178 # compatibility).
169- return int (istart + int (self .random ()* (istop - istart )))
179+
180+ if width >= maxwidth :
181+ return int (istart + self ._randbelow (width ))
182+ return int (istart + int (self .random ()* width ))
170183 if step == 1 :
171- raise ValueError , "empty range for randrange()"
184+ raise ValueError , "empty range for randrange() (%d,%d, %d)" % ( istart , istop , width )
172185
173186 # Non-unit step argument supplied.
174187 istep = int (step )
175188 if istep != step :
176189 raise ValueError , "non-integer step for randrange()"
177190 if istep > 0 :
178- n = (istop - istart + istep - 1 ) / istep
191+ n = (width + istep - 1 ) / istep
179192 elif istep < 0 :
180- n = (istop - istart + istep + 1 ) / istep
193+ n = (width + istep + 1 ) / istep
181194 else :
182195 raise ValueError , "zero step for randrange()"
183196
184197 if n <= 0 :
185198 raise ValueError , "empty range for randrange()"
199+
200+ if n >= maxwidth :
201+ return istart + self ._randbelow (n )
186202 return istart + istep * int (self .random () * n )
187203
188204 def randint (self , a , b ):
@@ -191,6 +207,33 @@ def randint(self, a, b):
191207
192208 return self .randrange (a , b + 1 )
193209
210+ def _randbelow (self , n , _log = _log , int = int , _maxwidth = 1L << BPF ,
211+ _Method = _MethodType , _BuiltinMethod = _BuiltinMethodType ):
212+ """Return a random int in the range [0,n)
213+
214+ Handles the case where n has more bits than returned
215+ by a single call to the underlying generator.
216+ """
217+
218+ try :
219+ getrandbits = self .getrandbits
220+ except AttributeError :
221+ pass
222+ else :
223+ # Only call self.getrandbits if the original random() builtin method
224+ # has not been overridden or if a new getrandbits() was supplied.
225+ # This assures that the two methods correspond.
226+ if type (self .random ) is _BuiltinMethod or type (getrandbits ) is _Method :
227+ k = int (1.00001 + _log (n - 1 , 2.0 )) # 2**k > n-1 > 2**(k-2)
228+ r = getrandbits (k )
229+ while r >= n :
230+ r = getrandbits (k )
231+ return r
232+ if n >= _maxwidth :
233+ _warn ("Underlying random() generator does not supply \n "
234+ "enough bits to choose from a population range this large" )
235+ return int (self .random () * n )
236+
194237## -------------------- sequence methods -------------------
195238
196239 def choice (self , seq ):
@@ -757,6 +800,7 @@ def _test(N=2000):
757800getstate = _inst .getstate
758801setstate = _inst .setstate
759802jumpahead = _inst .jumpahead
803+ getrandbits = _inst .getrandbits
760804
761805if __name__ == '__main__' :
762806 _test ()
0 commit comments