Change random.seed() so that it can get at the full range of possible

internal states.  Put the old .seed() (which could only get at about
the square root of the # of possibilities) under the new name .whseed(),
for bit-level compatibility with older versions.  This occurred to me
while reviewing effbot's book (he found himself stumbling over .seed()
more than once there ...).
This commit is contained in:
Tim Peters 2001-02-01 04:59:18 +00:00
parent 0eb107068a
commit 0de88fc4b1
3 changed files with 106 additions and 51 deletions

View File

@ -25,7 +25,8 @@ numbers it generates before repeating the sequence exactly) is
6,953,607,871,644. While of much higher quality than the \function{rand()} 6,953,607,871,644. While of much higher quality than the \function{rand()}
function supplied by most C libraries, the theoretical properties function supplied by most C libraries, the theoretical properties
are much the same as for a single linear congruential generator of are much the same as for a single linear congruential generator of
large modulus. large modulus. It is not suitable for all purposes, and is completely
unsuitable for cryptographic purposes.
The functions in this module are not threadsafe: if you want to call these The functions in this module are not threadsafe: if you want to call these
functions from multiple threads, you should explicitly serialize the calls. functions from multiple threads, you should explicitly serialize the calls.
@ -72,7 +73,7 @@ gens = create_generators(10, 1000000)
That creates 10 distinct generators, which can be passed out to 10 distinct That creates 10 distinct generators, which can be passed out to 10 distinct
threads. The generators don't share state so can be called safely in threads. The generators don't share state so can be called safely in
parallel. So long as no thread calls its \code{g.random()} more than a parallel. So long as no thread calls its \code{g.random()} more than a
million times (the second argument to \function{create_generators}), the million times (the second argument to \function{create_generators), the
sequences seen by each thread will not overlap. The period of the sequences seen by each thread will not overlap. The period of the
underlying Wichmann-Hill generator limits how far this technique can be underlying Wichmann-Hill generator limits how far this technique can be
pushed. pushed.
@ -83,10 +84,10 @@ also be used to "move backward in time":
\begin{verbatim} \begin{verbatim}
>>> g = Random(42) # arbitrary >>> g = Random(42) # arbitrary
>>> g.random() >>> g.random()
0.24855401895528142 0.25420336316883324
>>> g.jumpahead(6953607871644L - 1) # move *back* one >>> g.jumpahead(6953607871644L - 1) # move *back* one
>>> g.random() >>> g.random()
0.24855401895528142 0.25420336316883324
\end{verbatim} \end{verbatim}
@ -94,25 +95,38 @@ Bookkeeping functions:
\begin{funcdesc}{seed}{\optional{x}} \begin{funcdesc}{seed}{\optional{x}}
Initialize the basic random number generator. Initialize the basic random number generator.
Optional argument \var{x} can be any hashable object, Optional argument \var{x} can be any hashable object.
and the generator is seeded from its hash code. If \var(x) is omitted or \code{None}, current system time is used;
It is not guaranteed that distinct hash codes will produce distinct current system time is also used to initialize the generator when the
seeds. module is first imported.
If \var{x} is omitted or \code{None}, If \var(x) is not \code{None} or an int or long,
the seed is derived from the current system time. \code{hash(\var{x})) is used instead.
The seed is also set from the current system time when If \var{x} is an int or long, \var{x} is used directly.
the module is first imported. Distinct values between 0 and 27814431486575L inclusive are guaranteed
to yield distinct internal states (this guarantee is specific to the
default Wichmann-Hill generator, and may not apply to subclasses
supplying their own basic generator).
\end{funcdesc}
\begin{funcdesc}{whseed}{\optional{x}}
This is obsolete, supplied for bit-level compatibility with versions
of Python prior to 2.1.
See \function{seed} for details. \function{whseed} does not guarantee
that distinct integer arguments yield distinct internal states, and can
yield no more than about 2**24 distinct internal states in all.
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{getstate}{} \begin{funcdesc}{getstate}{}
Return an object capturing the current internal state of the generator. Return an object capturing the current internal state of the generator.
This object can be passed to \code{setstate()} to restore the state. This object can be passed to \code{setstate()} to restore the state.
\end{funcdesc} \versionadded{2.1}
\end{funcdesc}
\begin{funcdesc}{setstate}{state} \begin{funcdesc}{setstate}{state}
\var{state} should have been obtained from a previous call to \var{state} should have been obtained from a previous call to
\code{getstate()}, and \code{setstate()} restores the internal state \code{getstate()}, and \code{setstate()} restores the internal state
of the generate to what it was at the time \code{setstate()} was called. of the generator to what it was at the time \code{setstate()} was called.
\versionadded{2.1}
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{jumpahead}{n} \begin{funcdesc}{jumpahead}{n}
@ -124,6 +138,7 @@ Bookkeeping functions:
internal state, and then \method{jumpahead()} can be used to force the internal state, and then \method{jumpahead()} can be used to force the
instances' states as far apart as you like (up to the period of the instances' states as far apart as you like (up to the period of the
generator). generator).
\versionadded{2.1}
\end{funcdesc} \end{funcdesc}
Functions for integers: Functions for integers:

View File

@ -66,10 +66,10 @@ used to "move backward in time":
>>> g = Random(42) # arbitrary >>> g = Random(42) # arbitrary
>>> g.random() >>> g.random()
0.24855401895528142 0.25420336316883324
>>> g.jumpahead(6953607871644L - 1) # move *back* one >>> g.jumpahead(6953607871644L - 1) # move *back* one
>>> g.random() >>> g.random()
0.24855401895528142 0.25420336316883324
""" """
# XXX The docstring sucks. # XXX The docstring sucks.
@ -119,26 +119,31 @@ class Random:
# different core generator should override the seed(), random(), # different core generator should override the seed(), random(),
# getstate(), setstate() and jumpahead() methods. # getstate(), setstate() and jumpahead() methods.
def __whseed(self, x=0, y=0, z=0): def seed(self, a=None):
"""Set the Wichmann-Hill seed from (x, y, z). """Initialize internal state from hashable object.
These must be integers in the range [0, 256). None or no argument seeds from current time.
If a is not None or an int or long, hash(a) is instead.
If a is an int or long, a is used directly. Distinct values between
0 and 27814431486575L inclusive are guaranteed to yield distinct
internal states (this guarantee is specific to the default
Wichmann-Hill generator).
""" """
if not type(x) == type(y) == type(z) == type(0): if a is None:
raise TypeError('seeds must be integers')
if not (0 <= x < 256 and 0 <= y < 256 and 0 <= z < 256):
raise ValueError('seeds must be in range(0, 256)')
if 0 == x == y == z:
# Initialize from current time # Initialize from current time
import time import time
t = long(time.time()) * 256 a = long(time.time() * 256)
t = int((t&0xffffff) ^ (t>>24))
t, x = divmod(t, 256) if type(a) not in (type(3), type(3L)):
t, y = divmod(t, 256) a = hash(a)
t, z = divmod(t, 256)
# Zero is a poor seed, so substitute 1 a, x = divmod(a, 30268)
self._seed = (x or 1, y or 1, z or 1) a, y = divmod(a, 30306)
a, z = divmod(a, 30322)
self._seed = int(x)+1, int(y)+1, int(z)+1
def random(self): def random(self):
"""Get the next random number in the range [0.0, 1.0).""" """Get the next random number in the range [0.0, 1.0)."""
@ -171,26 +176,6 @@ class Random:
# never return 0.0 (asserted by Tim; proof too long for a comment). # never return 0.0 (asserted by Tim; proof too long for a comment).
return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0 return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0
def seed(self, a=None):
"""Seed from hashable object's hash code.
None or no argument seeds from current time. It is not guaranteed
that objects with distinct hash codes lead to distinct internal
states.
"""
if a is None:
self.__whseed()
return
a = hash(a)
a, x = divmod(a, 256)
a, y = divmod(a, 256)
a, z = divmod(a, 256)
x = (x + a) % 256 or 1
y = (y + a) % 256 or 1
z = (z + a) % 256 or 1
self.__whseed(x, y, z)
def getstate(self): def getstate(self):
"""Return internal state; can be passed to setstate() later.""" """Return internal state; can be passed to setstate() later."""
return self.VERSION, self._seed, self.gauss_next return self.VERSION, self._seed, self.gauss_next
@ -227,6 +212,50 @@ class Random:
z = int(z * pow(170, n, 30323)) % 30323 z = int(z * pow(170, n, 30323)) % 30323
self._seed = x, y, z self._seed = x, y, z
def __whseed(self, x=0, y=0, z=0):
"""Set the Wichmann-Hill seed from (x, y, z).
These must be integers in the range [0, 256).
"""
if not type(x) == type(y) == type(z) == type(0):
raise TypeError('seeds must be integers')
if not (0 <= x < 256 and 0 <= y < 256 and 0 <= z < 256):
raise ValueError('seeds must be in range(0, 256)')
if 0 == x == y == z:
# Initialize from current time
import time
t = long(time.time() * 256)
t = int((t&0xffffff) ^ (t>>24))
t, x = divmod(t, 256)
t, y = divmod(t, 256)
t, z = divmod(t, 256)
# Zero is a poor seed, so substitute 1
self._seed = (x or 1, y or 1, z or 1)
def whseed(self, a=None):
"""Seed from hashable object's hash code.
None or no argument seeds from current time. It is not guaranteed
that objects with distinct hash codes lead to distinct internal
states.
This is obsolete, provided for compatibility with the seed routine
used prior to Python 2.1. Use the .seed() method instead.
"""
if a is None:
self.__whseed()
return
a = hash(a)
a, x = divmod(a, 256)
a, y = divmod(a, 256)
a, z = divmod(a, 256)
x = (x + a) % 256 or 1
y = (y + a) % 256 or 1
z = (z + a) % 256 or 1
self.__whseed(x, y, z)
## ---- Methods below this point do not need to be overridden when ## ---- Methods below this point do not need to be overridden when
## ---- subclassing for the purpose of using a different core generator. ## ---- subclassing for the purpose of using a different core generator.
@ -623,6 +652,7 @@ weibullvariate = _inst.weibullvariate
getstate = _inst.getstate getstate = _inst.getstate
setstate = _inst.setstate setstate = _inst.setstate
jumpahead = _inst.jumpahead jumpahead = _inst.jumpahead
whseed = _inst.whseed
if __name__ == '__main__': if __name__ == '__main__':
_test() _test()

View File

@ -32,6 +32,16 @@ Standard library
each thread, then using .jumpahead() to force each instance to use a each thread, then using .jumpahead() to force each instance to use a
non-overlapping segment of the full period. non-overlapping segment of the full period.
- random.py's seed() function is new. For bit-for-bit compatibility with
prior releases, use the whseed function instead. The new seed function
addresses two problems: (1) The old function couldn't produce more than
about 2**24 distinct internal states; the new one about 2**45 (the best
that can be done in the Wichmann-Hill generator). (2) The old function
sometimes produced identical internal states when passed distinct
integers, and there was no simple way to predict when that would happen;
the new one guarantees to produce distinct internal states for all
arguments in [0, 27814431486576L).
Windows changes Windows changes
- Build procedure: the zlib project is built in a different way that - Build procedure: the zlib project is built in a different way that