Skip to content

Commit

Permalink
Merge pull request numpy#20314 from WarrenWeckesser/float32-rand-unus…
Browse files Browse the repository at this point in the history
…ed-bit

BUG: Get full precision for 32 bit floating point random values.
  • Loading branch information
seberg authored Nov 12, 2021
2 parents 9c28152 + e5af24d commit 1995e2c
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 6 deletions.
10 changes: 10 additions & 0 deletions doc/release/upcoming_changes/20314.change.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Change in generation of random 32 bit floating point variates
-------------------------------------------------------------
There was bug in the generation of 32 bit floating point values from
the uniform distribution that would result in the least significant
bit of the random variate always being 0. This has been fixed.

This change affects the variates produced by the `random.Generator`
methods ``random``, ``standard_normal``, ``standard_exponential``, and
``standard_gamma``, but only when the dtype is specified as
``numpy.float32``.
2 changes: 1 addition & 1 deletion numpy/random/src/distributions/distributions.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ static NPY_INLINE uint64_t next_uint64(bitgen_t *bitgen_state) {
}

static NPY_INLINE float next_float(bitgen_t *bitgen_state) {
return (next_uint32(bitgen_state) >> 9) * (1.0f / 8388608.0f);
return (next_uint32(bitgen_state) >> 8) * (1.0f / 16777216.0f);
}

/* Random generators for external use */
Expand Down
13 changes: 8 additions & 5 deletions numpy/random/tests/test_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,27 @@ def assert_state_equal(actual, target):
assert actual[key] == target[key]


def uint32_to_float32(u):
return ((u >> np.uint32(8)) * (1.0 / 2**24)).astype(np.float32)


def uniform32_from_uint64(x):
x = np.uint64(x)
upper = np.array(x >> np.uint64(32), dtype=np.uint32)
lower = np.uint64(0xffffffff)
lower = np.array(x & lower, dtype=np.uint32)
joined = np.column_stack([lower, upper]).ravel()
out = (joined >> np.uint32(9)) * (1.0 / 2 ** 23)
return out.astype(np.float32)
return uint32_to_float32(joined)


def uniform32_from_uint53(x):
x = np.uint64(x) >> np.uint64(16)
x = np.uint32(x & np.uint64(0xffffffff))
out = (x >> np.uint32(9)) * (1.0 / 2 ** 23)
return out.astype(np.float32)
return uint32_to_float32(x)


def uniform32_from_uint32(x):
return (x >> np.uint32(9)) * (1.0 / 2 ** 23)
return uint32_to_float32(x)


def uniform32_from_uint(x, bits):
Expand Down Expand Up @@ -126,6 +128,7 @@ def gauss_from_uint(x, n, bits):

return gauss[:n]


def test_seedsequence():
from numpy.random.bit_generator import (ISeedSequence,
ISpawnableSeedSequence,
Expand Down
12 changes: 12 additions & 0 deletions numpy/random/tests/test_generator_mt19937.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,18 @@ def test_random_float_scalar(self):
desired = 0.0969992
assert_array_almost_equal(actual, desired, decimal=7)

@pytest.mark.parametrize('dtype, uint_view_type',
[(np.float32, np.uint32),
(np.float64, np.uint64)])
def test_random_distribution_of_lsb(self, dtype, uint_view_type):
random = Generator(MT19937(self.seed))
sample = random.random(100000, dtype=dtype)
num_ones_in_lsb = np.count_nonzero(sample.view(uint_view_type) & 1)
# The probability of a 1 in the least significant bit is 0.25.
# With a sample size of 100000, the probability that num_ones_in_lsb
# is outside the following range is less than 5e-11.
assert 24100 < num_ones_in_lsb < 25900

def test_random_unsupported_type(self):
assert_raises(TypeError, random.random, dtype='int32')

Expand Down

0 comments on commit 1995e2c

Please sign in to comment.