diff options
author | gfyoung <gfyoung17@gmail.com> | 2017-03-26 14:30:28 -0700 |
---|---|---|
committer | gfyoung <gfyoung17@gmail.com> | 2017-05-09 03:12:52 -0400 |
commit | 1f88730b3140af1efb7d2e789d21078ad66c8eee (patch) | |
tree | b5bf76ed7ce34652098fd65e99e59a94337c5c98 /numpy/random | |
parent | 23b0cf3a5925dbc1f0503fbdacdf8088372f4ab5 (diff) | |
download | python-numpy-1f88730b3140af1efb7d2e789d21078ad66c8eee.tar.gz python-numpy-1f88730b3140af1efb7d2e789d21078ad66c8eee.tar.bz2 python-numpy-1f88730b3140af1efb7d2e789d21078ad66c8eee.zip |
BUG: Buttress handling of extreme values in randint
Diffstat (limited to 'numpy/random')
-rw-r--r-- | numpy/random/mtrand/mtrand.pyx | 21 | ||||
-rw-r--r-- | numpy/random/tests/test_random.py | 45 | ||||
-rw-r--r-- | numpy/random/tests/test_regression.py | 9 |
3 files changed, 60 insertions, 15 deletions
diff --git a/numpy/random/mtrand/mtrand.pyx b/numpy/random/mtrand/mtrand.pyx index 9f901f14c..c0082a782 100644 --- a/numpy/random/mtrand/mtrand.pyx +++ b/numpy/random/mtrand/mtrand.pyx @@ -964,20 +964,31 @@ cdef class RandomState: high = low low = 0 + # '_randint_type' is defined in + # 'generate_randint_helpers.py' key = np.dtype(dtype).name - if not key in _randint_type: + if key not in _randint_type: raise TypeError('Unsupported dtype "%s" for randint' % key) + lowbnd, highbnd, randfunc = _randint_type[key] - if low < lowbnd: + # TODO: Do not cast these inputs to Python int + # + # This is a workaround until gh-8851 is resolved (bug in NumPy + # integer comparison and subtraction involving uint64 and non- + # uint64). Afterwards, remove these two lines. + ilow = int(low) + ihigh = int(high) + + if ilow < lowbnd: raise ValueError("low is out of bounds for %s" % (key,)) - if high > highbnd: + if ihigh > highbnd: raise ValueError("high is out of bounds for %s" % (key,)) - if low >= high: + if ilow >= ihigh: raise ValueError("low >= high") with self.lock: - ret = randfunc(low, high - 1, size, self.state_address) + ret = randfunc(ilow, ihigh - 1, size, self.state_address) if size is None: if dtype in (np.bool, np.int, np.long): diff --git a/numpy/random/tests/test_random.py b/numpy/random/tests/test_random.py index e10c442a5..0e7396494 100644 --- a/numpy/random/tests/test_random.py +++ b/numpy/random/tests/test_random.py @@ -157,22 +157,42 @@ class TestRandint(TestCase): for dt in self.itype: lbnd = 0 if dt is np.bool_ else np.iinfo(dt).min ubnd = 2 if dt is np.bool_ else np.iinfo(dt).max + 1 + tgt = ubnd - 1 assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + tgt = lbnd assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + tgt = (lbnd + ubnd)//2 assert_equal(self.rfunc(tgt, tgt + 1, size=1000, dtype=dt), tgt) + def test_full_range(self): + # Test for ticket #1690 + + for dt in self.itype: + lbnd = 0 if dt is np.bool_ else np.iinfo(dt).min + ubnd = 2 if dt is np.bool_ else np.iinfo(dt).max + 1 + + try: + self.rfunc(lbnd, ubnd, dtype=dt) + except Exception as e: + raise AssertionError("No error should have been raised, " + "but one was with the following " + "message:\n\n%s" % str(e)) + def test_in_bounds_fuzz(self): # Don't use fixed seed np.random.seed() + for dt in self.itype[1:]: for ubnd in [4, 8, 16]: vals = self.rfunc(2, ubnd, size=2**16, dtype=dt) assert_(vals.max() < ubnd) assert_(vals.min() >= 2) - vals = self.rfunc(0, 2, size=2**16, dtype=np.bool) + + vals = self.rfunc(0, 2, size=2**16, dtype=np.bool_) + assert_(vals.max() < 2) assert_(vals.min() >= 0) @@ -209,6 +229,29 @@ class TestRandint(TestCase): res = hashlib.md5(val).hexdigest() assert_(tgt[np.dtype(np.bool).name] == res) + def test_int64_uint64_corner_case(self): + # When stored in Numpy arrays, `lbnd` is casted + # as np.int64, and `ubnd` is casted as np.uint64. + # Checking whether `lbnd` >= `ubnd` used to be + # done solely via direct comparison, which is incorrect + # because when Numpy tries to compare both numbers, + # it casts both to np.float64 because there is + # no integer superset of np.int64 and np.uint64. However, + # `ubnd` is too large to be represented in np.float64, + # causing it be round down to np.iinfo(np.int64).max, + # leading to a ValueError because `lbnd` now equals + # the new `ubnd`. + + dt = np.int64 + tgt = np.iinfo(np.int64).max + lbnd = np.int64(np.iinfo(np.int64).max) + ubnd = np.uint64(np.iinfo(np.int64).max + 1) + + # None of these function calls should + # generate a ValueError now. + actual = np.random.randint(lbnd, ubnd, dtype=dt) + assert_equal(actual, tgt) + def test_respect_dtype_singleton(self): # See gh-7203 for dt in self.itype: diff --git a/numpy/random/tests/test_regression.py b/numpy/random/tests/test_regression.py index b50b6b260..ce435b374 100644 --- a/numpy/random/tests/test_regression.py +++ b/numpy/random/tests/test_regression.py @@ -55,15 +55,6 @@ class TestRegression(TestCase): b = np.random.permutation(long(12)) assert_array_equal(a, b) - def test_randint_range(self): - # Test for ticket #1690 - lmax = np.iinfo('l').max - lmin = np.iinfo('l').min - try: - random.randint(lmin, lmax) - except: - raise AssertionError - def test_shuffle_mixed_dimension(self): # Test for trac ticket #2074 for t in [[1, 2, 3, None], |