diff options
| author | Sebastian Berg <sebastian@sipsolutions.net> | 2021-11-12 12:47:48 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-12 12:47:48 -0600 |
| commit | 1995e2cdfd17ec75e37cc6e69a42ce9000aac966 (patch) | |
| tree | 2fd5e74577d67e035dac297cf69fa518438a657d | |
| parent | 9c28152850109cdb567eaab7a872aa1429e51f55 (diff) | |
| parent | e5af24d51a4835f2a32220f8d7a5e673fd2638b1 (diff) | |
| download | numpy-1995e2cdfd17ec75e37cc6e69a42ce9000aac966.tar.gz | |
Merge pull request #20314 from WarrenWeckesser/float32-rand-unused-bit
BUG: Get full precision for 32 bit floating point random values.
| -rw-r--r-- | doc/release/upcoming_changes/20314.change.rst | 10 | ||||
| -rw-r--r-- | numpy/random/src/distributions/distributions.c | 2 | ||||
| -rw-r--r-- | numpy/random/tests/test_direct.py | 13 | ||||
| -rw-r--r-- | numpy/random/tests/test_generator_mt19937.py | 12 |
4 files changed, 31 insertions, 6 deletions
diff --git a/doc/release/upcoming_changes/20314.change.rst b/doc/release/upcoming_changes/20314.change.rst new file mode 100644 index 000000000..ea7e29aff --- /dev/null +++ b/doc/release/upcoming_changes/20314.change.rst @@ -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``. diff --git a/numpy/random/src/distributions/distributions.c b/numpy/random/src/distributions/distributions.c index adf4db4a7..bd1e1faa4 100644 --- a/numpy/random/src/distributions/distributions.c +++ b/numpy/random/src/distributions/distributions.c @@ -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 */ diff --git a/numpy/random/tests/test_direct.py b/numpy/random/tests/test_direct.py index ea1ebacb6..58d966adf 100644 --- a/numpy/random/tests/test_direct.py +++ b/numpy/random/tests/test_direct.py @@ -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): @@ -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, diff --git a/numpy/random/tests/test_generator_mt19937.py b/numpy/random/tests/test_generator_mt19937.py index 7ddccaf86..d057122f1 100644 --- a/numpy/random/tests/test_generator_mt19937.py +++ b/numpy/random/tests/test_generator_mt19937.py @@ -774,6 +774,18 @@ class TestRandomDist: 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') |
