Skip to content

Commit

Permalink
BUG: random: Generator.integers(2**32) always returned 0.
Browse files Browse the repository at this point in the history
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 numpygh-16066.
  • Loading branch information
WarrenWeckesser authored and charris committed Apr 27, 2020
1 parent 61041c7 commit 23d7021
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 11 deletions.
42 changes: 31 additions & 11 deletions numpy/random/src/distributions/distributions.c
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions numpy/random/tests/test_generator_mt19937.py
Expand Up @@ -521,6 +521,21 @@ def test_repeatability_broadcasting(self, endpoint):

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),
Expand Down

0 comments on commit 23d7021

Please sign in to comment.