Thanks to visit codestin.com
Credit goes to github.com

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)