summaryrefslogtreecommitdiff
path: root/numpy/random
diff options
context:
space:
mode:
authorWarren Weckesser <warren.weckesser@gmail.com>2020-04-25 12:28:57 -0400
committerWarren Weckesser <warren.weckesser@gmail.com>2020-04-26 16:43:03 -0400
commite8b84012463927a1fc9c5a236ce7c4ab4a7effaa (patch)
treed47cfce8d5719302050ee0eff35670e024c34937 /numpy/random
parent87964f1c5738492b152e80f7e38010c69214e5ae (diff)
downloadnumpy-e8b84012463927a1fc9c5a236ce7c4ab4a7effaa.tar.gz
BUG: random: Generator.integers(2**32) always returned 0.
When the input to Generator.integers was 2**32, the value 2**32-1 was being passed as the `rng` argument to the 32-bit Lemire method, but that method requires `rng` be strictly less then 2**32-1. The fix was to handle 2**32-1 by calling next_uint32 directly. This also works for the legacy code without changing the stream of random integers from `randint`. Closes gh-16066.
Diffstat (limited to 'numpy/random')
-rw-r--r--numpy/random/src/distributions/distributions.c42
-rw-r--r--numpy/random/tests/test_generator_mt19937.py15
2 files changed, 46 insertions, 11 deletions
diff --git a/numpy/random/src/distributions/distributions.c b/numpy/random/src/distributions/distributions.c
index 0b46dc6d8..d3ea64735 100644
--- a/numpy/random/src/distributions/distributions.c
+++ b/numpy/random/src/distributions/distributions.c
@@ -1337,6 +1337,14 @@ uint64_t random_bounded_uint64(bitgen_t *bitgen_state, uint64_t off,
return off;
} else if (rng <= 0xFFFFFFFFUL) {
/* Call 32-bit generator if range in 32-bit. */
+ if (rng == 0xFFFFFFFFUL) {
+ /*
+ * The 32-bit Lemire method does not handle rng=0xFFFFFFFF, so we'll
+ * call next_uint32 directly. This also works when use_masked is True,
+ * so we handle both cases here.
+ */
+ return off + (uint64_t) next_uint32(bitgen_state);
+ }
if (use_masked) {
return off + buffered_bounded_masked_uint32(bitgen_state, rng, mask, NULL,
NULL);
@@ -1450,22 +1458,34 @@ void random_bounded_uint64_fill(bitgen_t *bitgen_state, uint64_t off,
out[i] = off;
}
} else if (rng <= 0xFFFFFFFFUL) {
- uint32_t buf = 0;
- int bcnt = 0;
-
/* Call 32-bit generator if range in 32-bit. */
- if (use_masked) {
- /* Smallest bit mask >= max */
- uint64_t mask = gen_mask(rng);
+ /*
+ * The 32-bit Lemire method does not handle rng=0xFFFFFFFF, so we'll
+ * call next_uint32 directly. This also works when use_masked is True,
+ * so we handle both cases here.
+ */
+ if (rng == 0xFFFFFFFFUL) {
for (i = 0; i < cnt; i++) {
- out[i] = off + buffered_bounded_masked_uint32(bitgen_state, rng, mask,
- &bcnt, &buf);
+ out[i] = off + (uint64_t) next_uint32(bitgen_state);
}
} else {
- for (i = 0; i < cnt; i++) {
- out[i] = off +
- buffered_bounded_lemire_uint32(bitgen_state, rng, &bcnt, &buf);
+ uint32_t buf = 0;
+ int bcnt = 0;
+
+ if (use_masked) {
+ /* Smallest bit mask >= max */
+ uint64_t mask = gen_mask(rng);
+
+ for (i = 0; i < cnt; i++) {
+ out[i] = off + buffered_bounded_masked_uint32(bitgen_state, rng, mask,
+ &bcnt, &buf);
+ }
+ } else {
+ for (i = 0; i < cnt; i++) {
+ out[i] = off +
+ buffered_bounded_lemire_uint32(bitgen_state, rng, &bcnt, &buf);
+ }
}
}
} else if (rng == 0xFFFFFFFFFFFFFFFFULL) {
diff --git a/numpy/random/tests/test_generator_mt19937.py b/numpy/random/tests/test_generator_mt19937.py
index 3e820d9d7..aa7dd45e9 100644
--- a/numpy/random/tests/test_generator_mt19937.py
+++ b/numpy/random/tests/test_generator_mt19937.py
@@ -520,6 +520,21 @@ class TestIntegers:
assert_array_equal(val, val_bc)
+ @pytest.mark.parametrize(
+ 'bound, expected',
+ [(2**32 - 1, np.array([517043486, 1364798665, 1733884389, 1353720612,
+ 3769704066, 1170797179, 4108474671])),
+ (2**32, np.array([517043487, 1364798666, 1733884390, 1353720613,
+ 3769704067, 1170797180, 4108474672])),
+ (2**32 + 1, np.array([517043487, 1733884390, 3769704068, 4108474673,
+ 1831631863, 1215661561, 3869512430]))]
+ )
+ def test_repeatability_32bit_boundary(self, bound, expected):
+ for size in [None, len(expected)]:
+ random = Generator(MT19937(1234))
+ x = random.integers(bound, size=size)
+ assert_equal(x, expected if size is not None else expected[0])
+
def test_int64_uint64_broadcast_exceptions(self, endpoint):
configs = {np.uint64: ((0, 2**65), (-1, 2**62), (10, 9), (0, 0)),
np.int64: ((0, 2**64), (-(2**64), 2**62), (10, 9), (0, 0),