Skip to content

Commit 4b9e569

Browse files
BUG: Get full precision for 32 bit floating point random values.
The formula to convert a 32 bit random integer to a random float32, (next_uint32(bitgen_state) >> 9) * (1.0f / 8388608.0f) shifts by one bit too many, resulting in uniform float32 samples always having a 0 in the least significant bit. The formula is corrected to (next_uint32(bitgen_state) >> 8) * (1.0f / 16777216.0f) Occurrences of the incorrect formula in numpy/random/tests/test_direct.py were also corrected. Closes numpygh-17478.
1 parent 7c21101 commit 4b9e569

File tree

3 files changed

+21
-6
lines changed

3 files changed

+21
-6
lines changed

numpy/random/src/distributions/distributions.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ static NPY_INLINE uint64_t next_uint64(bitgen_t *bitgen_state) {
1717
}
1818

1919
static NPY_INLINE float next_float(bitgen_t *bitgen_state) {
20-
return (next_uint32(bitgen_state) >> 9) * (1.0f / 8388608.0f);
20+
return (next_uint32(bitgen_state) >> 8) * (1.0f / 16777216.0f);
2121
}
2222

2323
/* Random generators for external use */

numpy/random/tests/test_direct.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,27 @@ def assert_state_equal(actual, target):
4646
assert actual[key] == target[key]
4747

4848

49+
def uint32_to_float32(u):
50+
return ((u >> np.uint32(8)) * (1.0 / 2**24)).astype(np.float32)
51+
52+
4953
def uniform32_from_uint64(x):
5054
x = np.uint64(x)
5155
upper = np.array(x >> np.uint64(32), dtype=np.uint32)
5256
lower = np.uint64(0xffffffff)
5357
lower = np.array(x & lower, dtype=np.uint32)
5458
joined = np.column_stack([lower, upper]).ravel()
55-
out = (joined >> np.uint32(9)) * (1.0 / 2 ** 23)
56-
return out.astype(np.float32)
59+
return uint32_to_float32(joined)
5760

5861

5962
def uniform32_from_uint53(x):
6063
x = np.uint64(x) >> np.uint64(16)
6164
x = np.uint32(x & np.uint64(0xffffffff))
62-
out = (x >> np.uint32(9)) * (1.0 / 2 ** 23)
63-
return out.astype(np.float32)
65+
return uint32_to_float32(x)
6466

6567

6668
def uniform32_from_uint32(x):
67-
return (x >> np.uint32(9)) * (1.0 / 2 ** 23)
69+
return uint32_to_float32(x)
6870

6971

7072
def uniform32_from_uint(x, bits):
@@ -126,6 +128,7 @@ def gauss_from_uint(x, n, bits):
126128

127129
return gauss[:n]
128130

131+
129132
def test_seedsequence():
130133
from numpy.random.bit_generator import (ISeedSequence,
131134
ISpawnableSeedSequence,

numpy/random/tests/test_generator_mt19937.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,18 @@ def test_random_float_scalar(self):
774774
desired = 0.0969992
775775
assert_array_almost_equal(actual, desired, decimal=7)
776776

777+
@pytest.mark.parametrize('dtype, uint_view_type',
778+
[(np.float32, np.uint32),
779+
(np.float64, np.uint64)])
780+
def test_random_distribution_of_lsb(self, dtype, uint_view_type):
781+
random = Generator(MT19937(self.seed))
782+
sample = random.random(100000, dtype=dtype)
783+
num_ones_in_lsb = np.count_nonzero(sample.view(uint_view_type) & 1)
784+
# The probability of a 1 in the least significant bit is 0.25.
785+
# With a sample size of 100000, the probability that num_ones_in_lsb
786+
# is outside the following range is less than 5e-11.
787+
assert 24100 < num_ones_in_lsb < 25900
788+
777789
def test_random_unsupported_type(self):
778790
assert_raises(TypeError, random.random, dtype='int32')
779791

0 commit comments

Comments
 (0)