Skip to content

Commit 75a3378

Browse files
authored
bpo-40282: Allow random.getrandbits(0) (GH-19539)
1 parent d7c657d commit 75a3378

File tree

5 files changed

+42
-44
lines changed

5 files changed

+42
-44
lines changed

Doc/library/random.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ Bookkeeping functions
111111
as an optional part of the API. When available, :meth:`getrandbits` enables
112112
:meth:`randrange` to handle arbitrarily large ranges.
113113

114+
.. versionchanged:: 3.9
115+
This method now accepts zero for *k*.
116+
114117

115118
.. function:: randbytes(n)
116119

Lib/random.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ def randint(self, a, b):
261261
def _randbelow_with_getrandbits(self, n):
262262
"Return a random int in the range [0,n). Raises ValueError if n==0."
263263

264+
if not n:
265+
raise ValueError("Boundary cannot be zero")
264266
getrandbits = self.getrandbits
265267
k = n.bit_length() # don't use (n-1) here because n can be 1
266268
r = getrandbits(k) # 0 <= r < 2**k
@@ -733,8 +735,8 @@ def random(self):
733735

734736
def getrandbits(self, k):
735737
"""getrandbits(k) -> x. Generates an int with k random bits."""
736-
if k <= 0:
737-
raise ValueError('number of bits must be greater than zero')
738+
if k < 0:
739+
raise ValueError('number of bits must be non-negative')
738740
numbytes = (k + 7) // 8 # bits / 8 and rounded up
739741
x = int.from_bytes(_urandom(numbytes), 'big')
740742
return x >> (numbytes * 8 - k) # trim excess bits

Lib/test/test_random.py

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,31 @@ def test_gauss(self):
263263
self.assertEqual(x1, x2)
264264
self.assertEqual(y1, y2)
265265

266+
def test_getrandbits(self):
267+
# Verify ranges
268+
for k in range(1, 1000):
269+
self.assertTrue(0 <= self.gen.getrandbits(k) < 2**k)
270+
self.assertEqual(self.gen.getrandbits(0), 0)
271+
272+
# Verify all bits active
273+
getbits = self.gen.getrandbits
274+
for span in [1, 2, 3, 4, 31, 32, 32, 52, 53, 54, 119, 127, 128, 129]:
275+
all_bits = 2**span-1
276+
cum = 0
277+
cpl_cum = 0
278+
for i in range(100):
279+
v = getbits(span)
280+
cum |= v
281+
cpl_cum |= all_bits ^ v
282+
self.assertEqual(cum, all_bits)
283+
self.assertEqual(cpl_cum, all_bits)
284+
285+
# Verify argument checking
286+
self.assertRaises(TypeError, self.gen.getrandbits)
287+
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
288+
self.assertRaises(ValueError, self.gen.getrandbits, -1)
289+
self.assertRaises(TypeError, self.gen.getrandbits, 10.1)
290+
266291
def test_pickling(self):
267292
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
268293
state = pickle.dumps(self.gen, proto)
@@ -390,26 +415,6 @@ def test_randrange_errors(self):
390415
raises(0, 42, 0)
391416
raises(0, 42, 3.14159)
392417

393-
def test_genrandbits(self):
394-
# Verify ranges
395-
for k in range(1, 1000):
396-
self.assertTrue(0 <= self.gen.getrandbits(k) < 2**k)
397-
398-
# Verify all bits active
399-
getbits = self.gen.getrandbits
400-
for span in [1, 2, 3, 4, 31, 32, 32, 52, 53, 54, 119, 127, 128, 129]:
401-
cum = 0
402-
for i in range(100):
403-
cum |= getbits(span)
404-
self.assertEqual(cum, 2**span-1)
405-
406-
# Verify argument checking
407-
self.assertRaises(TypeError, self.gen.getrandbits)
408-
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
409-
self.assertRaises(ValueError, self.gen.getrandbits, 0)
410-
self.assertRaises(ValueError, self.gen.getrandbits, -1)
411-
self.assertRaises(TypeError, self.gen.getrandbits, 10.1)
412-
413418
def test_randbelow_logic(self, _log=log, int=int):
414419
# check bitcount transition points: 2**i and 2**(i+1)-1
415420
# show that: k = int(1.001 + _log(n, 2))
@@ -629,34 +634,18 @@ def test_rangelimits(self):
629634
self.assertEqual(set(range(start,stop)),
630635
set([self.gen.randrange(start,stop) for i in range(100)]))
631636

632-
def test_genrandbits(self):
637+
def test_getrandbits(self):
638+
super().test_getrandbits()
639+
633640
# Verify cross-platform repeatability
634641
self.gen.seed(1234567)
635642
self.assertEqual(self.gen.getrandbits(100),
636643
97904845777343510404718956115)
637-
# Verify ranges
638-
for k in range(1, 1000):
639-
self.assertTrue(0 <= self.gen.getrandbits(k) < 2**k)
640-
641-
# Verify all bits active
642-
getbits = self.gen.getrandbits
643-
for span in [1, 2, 3, 4, 31, 32, 32, 52, 53, 54, 119, 127, 128, 129]:
644-
cum = 0
645-
for i in range(100):
646-
cum |= getbits(span)
647-
self.assertEqual(cum, 2**span-1)
648-
649-
# Verify argument checking
650-
self.assertRaises(TypeError, self.gen.getrandbits)
651-
self.assertRaises(TypeError, self.gen.getrandbits, 'a')
652-
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
653-
self.assertRaises(ValueError, self.gen.getrandbits, 0)
654-
self.assertRaises(ValueError, self.gen.getrandbits, -1)
655644

656645
def test_randrange_uses_getrandbits(self):
657646
# Verify use of getrandbits by randrange
658647
# Use same seed as in the cross-platform repeatability test
659-
# in test_genrandbits above.
648+
# in test_getrandbits above.
660649
self.gen.seed(1234567)
661650
# If randrange uses getrandbits, it should pick getrandbits(100)
662651
# when called with a 100-bits stop argument.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow ``random.getrandbits(0)`` to succeed and to return 0.

Modules/_randommodule.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,12 +474,15 @@ _random_Random_getrandbits_impl(RandomObject *self, int k)
474474
uint32_t *wordarray;
475475
PyObject *result;
476476

477-
if (k <= 0) {
477+
if (k < 0) {
478478
PyErr_SetString(PyExc_ValueError,
479-
"number of bits must be greater than zero");
479+
"number of bits must be non-negative");
480480
return NULL;
481481
}
482482

483+
if (k == 0)
484+
return PyLong_FromLong(0);
485+
483486
if (k <= 32) /* Fast path */
484487
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));
485488

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy