diff options
author | Warren Weckesser <warren.weckesser@gmail.com> | 2020-04-25 12:28:57 -0400 |
---|---|---|
committer | Warren Weckesser <warren.weckesser@gmail.com> | 2020-04-26 16:43:03 -0400 |
commit | e8b84012463927a1fc9c5a236ce7c4ab4a7effaa (patch) | |
tree | d47cfce8d5719302050ee0eff35670e024c34937 /numpy/random | |
parent | 87964f1c5738492b152e80f7e38010c69214e5ae (diff) | |
download | numpy-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.c | 42 | ||||
-rw-r--r-- | numpy/random/tests/test_generator_mt19937.py | 15 |
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), |