Skip to content

Commit 1995e2c

Browse files
authored
Merge pull request numpy#20314 from WarrenWeckesser/float32-rand-unused-bit
BUG: Get full precision for 32 bit floating point random values.
2 parents 9c28152 + e5af24d commit 1995e2c

File tree

4 files changed

+31
-6
lines changed

4 files changed

+31
-6
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Change in generation of random 32 bit floating point variates
2+
-------------------------------------------------------------
3+
There was bug in the generation of 32 bit floating point values from
4+
the uniform distribution that would result in the least significant
5+
bit of the random variate always being 0. This has been fixed.
6+
7+
This change affects the variates produced by the `random.Generator`
8+
methods ``random``, ``standard_normal``, ``standard_exponential``, and
9+
``standard_gamma``, but only when the dtype is specified as
10+
``numpy.float32``.

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)