Skip to content

Commit 68784fe

Browse files
gh-133489: Remove size restrictions on getrandbits() and randbytes() (GH-133658)
random.getrandbits() can now generate more that 2**31 bits. random.randbytes() can now generate more that 256 MiB.
1 parent c6e63d9 commit 68784fe

File tree

5 files changed

+47
-26
lines changed

5 files changed

+47
-26
lines changed

Lib/test/test_random.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ def test_getrandbits(self):
392392
self.assertRaises(TypeError, self.gen.getrandbits)
393393
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
394394
self.assertRaises(ValueError, self.gen.getrandbits, -1)
395+
self.assertRaises(OverflowError, self.gen.getrandbits, 1<<1000)
396+
self.assertRaises(ValueError, self.gen.getrandbits, -1<<1000)
395397
self.assertRaises(TypeError, self.gen.getrandbits, 10.1)
396398

397399
def test_pickling(self):
@@ -435,6 +437,8 @@ def test_randbytes(self):
435437
self.assertRaises(TypeError, self.gen.randbytes)
436438
self.assertRaises(TypeError, self.gen.randbytes, 1, 2)
437439
self.assertRaises(ValueError, self.gen.randbytes, -1)
440+
self.assertRaises(OverflowError, self.gen.randbytes, 1<<1000)
441+
self.assertRaises((ValueError, OverflowError), self.gen.randbytes, -1<<1000)
438442
self.assertRaises(TypeError, self.gen.randbytes, 1.0)
439443

440444
def test_mu_sigma_default_args(self):
@@ -806,6 +810,22 @@ def test_getrandbits(self):
806810
self.assertEqual(self.gen.getrandbits(100),
807811
97904845777343510404718956115)
808812

813+
def test_getrandbits_2G_bits(self):
814+
size = 2**31
815+
self.gen.seed(1234567)
816+
x = self.gen.getrandbits(size)
817+
self.assertEqual(x.bit_length(), size)
818+
self.assertEqual(x & (2**100-1), 890186470919986886340158459475)
819+
self.assertEqual(x >> (size-100), 1226514312032729439655761284440)
820+
821+
@support.bigmemtest(size=2**32, memuse=1/8+2/15, dry_run=False)
822+
def test_getrandbits_4G_bits(self, size):
823+
self.gen.seed(1234568)
824+
x = self.gen.getrandbits(size)
825+
self.assertEqual(x.bit_length(), size)
826+
self.assertEqual(x & (2**100-1), 287241425661104632871036099814)
827+
self.assertEqual(x >> (size-100), 739728759900339699429794460738)
828+
809829
def test_randrange_uses_getrandbits(self):
810830
# Verify use of getrandbits by randrange
811831
# Use same seed as in the cross-platform repeatability test
@@ -962,6 +982,14 @@ def test_randbytes_getrandbits(self):
962982
self.assertEqual(self.gen.randbytes(n),
963983
gen2.getrandbits(n * 8).to_bytes(n, 'little'))
964984

985+
@support.bigmemtest(size=2**29, memuse=1+16/15, dry_run=False)
986+
def test_randbytes_256M(self, size):
987+
self.gen.seed(2849427419)
988+
x = self.gen.randbytes(size)
989+
self.assertEqual(len(x), size)
990+
self.assertEqual(x[:12].hex(), 'f6fd9ae63855ab91ea238b4f')
991+
self.assertEqual(x[-12:].hex(), '0e7af69a84ee99bf4a11becc')
992+
965993
def test_sample_counts_equivalence(self):
966994
# Test the documented strong equivalence to a sample with repeated elements.
967995
# We run this test on random.Random() which makes deterministic selections
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`random.getrandbits` can now generate more that 2\ :sup:`31` bits.
2+
:func:`random.randbytes` can now generate more that 256 MiB.

Modules/_randommodule.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -497,34 +497,32 @@ _random_Random_setstate_impl(RandomObject *self, PyObject *state)
497497
_random.Random.getrandbits
498498
499499
self: self(type="RandomObject *")
500-
k: int
500+
k: uint64
501501
/
502502
503503
getrandbits(k) -> x. Generates an int with k random bits.
504504
[clinic start generated code]*/
505505

506506
static PyObject *
507-
_random_Random_getrandbits_impl(RandomObject *self, int k)
508-
/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/
507+
_random_Random_getrandbits_impl(RandomObject *self, uint64_t k)
508+
/*[clinic end generated code: output=c30ef8435f3433cf input=64226ac13bb4d2a3]*/
509509
{
510-
int i, words;
510+
Py_ssize_t i, words;
511511
uint32_t r;
512512
uint32_t *wordarray;
513513
PyObject *result;
514514

515-
if (k < 0) {
516-
PyErr_SetString(PyExc_ValueError,
517-
"number of bits must be non-negative");
518-
return NULL;
519-
}
520-
521515
if (k == 0)
522516
return PyLong_FromLong(0);
523517

524518
if (k <= 32) /* Fast path */
525519
return PyLong_FromUnsignedLong(genrand_uint32(self) >> (32 - k));
526520

527-
words = (k - 1) / 32 + 1;
521+
if ((k - 1u) / 32u + 1u > PY_SSIZE_T_MAX / 4u) {
522+
PyErr_NoMemory();
523+
return NULL;
524+
}
525+
words = (k - 1u) / 32u + 1u;
528526
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
529527
if (wordarray == NULL) {
530528
PyErr_NoMemory();

Modules/clinic/_randommodule.c.h

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/longobject.c

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -971,16 +971,9 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n,
971971
++numsignificantbytes;
972972
}
973973

974-
/* How many Python int digits do we need? We have
975-
8*numsignificantbytes bits, and each Python int digit has
976-
PyLong_SHIFT bits, so it's the ceiling of the quotient. */
977-
/* catch overflow before it happens */
978-
if (numsignificantbytes > (PY_SSIZE_T_MAX - PyLong_SHIFT) / 8) {
979-
PyErr_SetString(PyExc_OverflowError,
980-
"byte array too long to convert to int");
981-
return NULL;
982-
}
983-
ndigits = (numsignificantbytes * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
974+
/* avoid integer overflow */
975+
ndigits = numsignificantbytes / PyLong_SHIFT * 8
976+
+ (numsignificantbytes % PyLong_SHIFT * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
984977
v = long_alloc(ndigits);
985978
if (v == NULL)
986979
return NULL;

0 commit comments

Comments
 (0)