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()}
function supplied by most C libraries, the theoretical properties
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
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
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
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
underlying Wichmann-Hill generator limits how far this technique can be
pushed.
@ -83,10 +84,10 @@ also be used to "move backward in time":
\begin{verbatim}
>>> g = Random(42) # arbitrary
>>> g.random()
0.24855401895528142
0.25420336316883324
>>> g.jumpahead(6953607871644L - 1) # move *back* one
>>> g.random()
0.24855401895528142
0.25420336316883324
\end{verbatim}
@ -94,25 +95,38 @@ Bookkeeping functions:
\begin{funcdesc}{seed}{\optional{x}}
Initialize the basic random number generator.
Optional argument \var{x} can be any hashable object,
and the generator is seeded from its hash code.
It is not guaranteed that distinct hash codes will produce distinct
seeds.
If \var{x} is omitted or \code{None},
the seed is derived from the current system time.
The seed is also set from the current system time when
the module is first imported.
Optional argument \var{x} can be any hashable object.
If \var(x) is omitted or \code{None}, current system time is used;
current system time is also used to initialize the generator when the
module is first imported.
If \var(x) is not \code{None} or an int or long,
\code{hash(\var{x})) is used instead.
If \var{x} is an int or long, \var{x} 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, 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}
\begin{funcdesc}{getstate}{}
Return an object capturing the current internal state of the generator.
This object can be passed to \code{setstate()} to restore the state.
\end{funcdesc}
\versionadded{2.1}
\end{funcdesc}
\begin{funcdesc}{setstate}{state}
\var{state} should have been obtained from a previous call to
\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}
\begin{funcdesc}{jumpahead}{n}
@ -124,6 +138,7 @@ Bookkeeping functions:
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
generator).
\versionadded{2.1}
\end{funcdesc}
Functions for integers:

View File

@ -66,10 +66,10 @@ used to "move backward in time":
>>> g = Random(42) # arbitrary
>>> g.random()
0.24855401895528142
0.25420336316883324
>>> g.jumpahead(6953607871644L - 1) # move *back* one
>>> g.random()
0.24855401895528142
0.25420336316883324
"""
# XXX The docstring sucks.
@ -119,26 +119,31 @@ class Random:
# different core generator should override the seed(), random(),
# getstate(), setstate() and jumpahead() methods.
def __whseed(self, x=0, y=0, z=0):
"""Set the Wichmann-Hill seed from (x, y, z).
def seed(self, a=None):
"""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):
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:
if a is None:
# 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)
a = long(time.time() * 256)
if type(a) not in (type(3), type(3L)):
a = hash(a)
a, x = divmod(a, 30268)
a, y = divmod(a, 30306)
a, z = divmod(a, 30322)
self._seed = int(x)+1, int(y)+1, int(z)+1
def random(self):
"""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).
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):
"""Return internal state; can be passed to setstate() later."""
return self.VERSION, self._seed, self.gauss_next
@ -227,6 +212,50 @@ class Random:
z = int(z * pow(170, n, 30323)) % 30323
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
## ---- subclassing for the purpose of using a different core generator.
@ -623,6 +652,7 @@ weibullvariate = _inst.weibullvariate
getstate = _inst.getstate
setstate = _inst.setstate
jumpahead = _inst.jumpahead
whseed = _inst.whseed
if __name__ == '__main__':
_test()

View File

@ -32,6 +32,16 @@ Standard library
each thread, then using .jumpahead() to force each instance to use a
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
- Build procedure: the zlib project is built in a different way that