diff options
-rw-r--r-- | doc/release/1.9.0-notes.rst | 8 | ||||
-rw-r--r-- | numpy/random/mtrand/mtrand.pyx | 11 | ||||
-rw-r--r-- | numpy/random/mtrand/numpy.pxd | 2 | ||||
-rw-r--r-- | numpy/random/tests/test_random.py | 29 |
4 files changed, 48 insertions, 2 deletions
diff --git a/doc/release/1.9.0-notes.rst b/doc/release/1.9.0-notes.rst index c856904af..9aa39342a 100644 --- a/doc/release/1.9.0-notes.rst +++ b/doc/release/1.9.0-notes.rst @@ -112,6 +112,14 @@ performed, and hence the sequence location will be different after a call to distribution.c::rk_binomial_btpe. Any tests which rely on the RNG being in a known state should be checked and/or updated as a result. +Random seed enforced to be a 32 bit unsigned integer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``np.random.seed`` and ``np.random.RandomState`` now throw a ``ValueError`` +if the seed cannot safely be converted to 32 bit unsigned integers. +Applications that now fail can be fixed by masking the higher 32 bit values to +zero: ``seed = seed & 0xFFFFFFFF``. This is what is done silently in older +versions so the random stream remains the same. + New Features ============ diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index 3f9dcb687..c2603543d 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -628,6 +628,7 @@ cdef class RandomState: ---------- seed : int or array_like, optional Seed for `RandomState`. + Must be convertable to 32 bit unsigned integers. See Also -------- @@ -640,9 +641,15 @@ cdef class RandomState: if seed is None: errcode = rk_randomseed(self.internal_state) else: - rk_seed(operator.index(seed), self.internal_state) + idx = operator.index(seed) + if idx > int(2**32 - 1) or idx < 0: + raise ValueError("Seed must be between 0 and 4294967295") + rk_seed(idx, self.internal_state) except TypeError: - obj = <ndarray>PyArray_ContiguousFromObject(seed, NPY_LONG, 1, 1) + obj = np.asarray(seed).astype(np.int64, casting='safe') + if ((obj > int(2**32 - 1)) | (obj < 0)).any(): + raise ValueError("Seed must be between 0 and 4294967295") + obj = obj.astype('L', casting='unsafe') init_by_array(self.internal_state, <unsigned long *>PyArray_DATA(obj), PyArray_DIM(obj, 0)) diff --git a/numpy/random/mtrand/numpy.pxd b/numpy/random/mtrand/numpy.pxd index 6812cc164..c54f79c0a 100644 --- a/numpy/random/mtrand/numpy.pxd +++ b/numpy/random/mtrand/numpy.pxd @@ -121,6 +121,8 @@ cdef extern from "numpy/arrayobject.h": object PyArray_IterNew(object arr) void PyArray_ITER_NEXT(flatiter it) nogil + dtype PyArray_DescrFromType(int) + void import_array() # include functions that were once macros in the new api diff --git a/numpy/random/tests/test_random.py b/numpy/random/tests/test_random.py index b6c4fe3af..ef4e7b127 100644 --- a/numpy/random/tests/test_random.py +++ b/numpy/random/tests/test_random.py @@ -7,6 +7,35 @@ from numpy.testing import ( from numpy import random from numpy.compat import asbytes +class TestSeed(TestCase): + def test_scalar(self): + s = np.random.RandomState(0) + assert_equal(s.randint(1000), 684) + s = np.random.RandomState(4294967295) + assert_equal(s.randint(1000), 419) + + def test_array(self): + s = np.random.RandomState(range(10)) + assert_equal(s.randint(1000), 468) + s = np.random.RandomState(np.arange(10)) + assert_equal(s.randint(1000), 468) + s = np.random.RandomState([0]) + assert_equal(s.randint(1000), 973) + s = np.random.RandomState([4294967295]) + assert_equal(s.randint(1000), 265) + + def test_invalid_scalar(self): + # seed must be a unsigned 32 bit integers + assert_raises(TypeError, np.random.RandomState, -0.5) + assert_raises(ValueError, np.random.RandomState, -1) + + def test_invalid_array(self): + # seed must be a unsigned 32 bit integers + assert_raises(TypeError, np.random.RandomState, [-0.5]) + assert_raises(ValueError, np.random.RandomState, [-1]) + assert_raises(ValueError, np.random.RandomState, [4294967296]) + assert_raises(ValueError, np.random.RandomState, [1, 2, 4294967296]) + assert_raises(ValueError, np.random.RandomState, [1, -2, 4294967296]) class TestBinomial(TestCase): def test_n_zero(self): |