Skip to content

Commit ce88d21

Browse files
[3.13] 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. (cherry picked from commit 68784fe) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 19b31d1 commit ce88d21

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
@@ -495,34 +495,32 @@ _random_Random_setstate_impl(RandomObject *self, PyObject *state)
495495
_random.Random.getrandbits
496496
497497
self: self(type="RandomObject *")
498-
k: int
498+
k: unsigned_long_long(bitwise=False)
499499
/
500500
501501
getrandbits(k) -> x. Generates an int with k random bits.
502502
[clinic start generated code]*/
503503

504504
static PyObject *
505-
_random_Random_getrandbits_impl(RandomObject *self, int k)
506-
/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/
505+
_random_Random_getrandbits_impl(RandomObject *self, unsigned long long k)
506+
/*[clinic end generated code: output=25a604fab95885d4 input=88e51091eea2f042]*/
507507
{
508-
int i, words;
508+
Py_ssize_t i, words;
509509
uint32_t r;
510510
uint32_t *wordarray;
511511
PyObject *result;
512512

513-
if (k < 0) {
514-
PyErr_SetString(PyExc_ValueError,
515-
"number of bits must be non-negative");
516-
return NULL;
517-
}
518-
519513
if (k == 0)
520514
return PyLong_FromLong(0);
521515

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

525-
words = (k - 1) / 32 + 1;
519+
if ((k - 1u) / 32u + 1u > PY_SSIZE_T_MAX / 4u) {
520+
PyErr_NoMemory();
521+
return NULL;
522+
}
523+
words = (k - 1u) / 32u + 1u;
526524
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
527525
if (wordarray == NULL) {
528526
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
@@ -874,16 +874,9 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n,
874874
++numsignificantbytes;
875875
}
876876

877-
/* How many Python int digits do we need? We have
878-
8*numsignificantbytes bits, and each Python int digit has
879-
PyLong_SHIFT bits, so it's the ceiling of the quotient. */
880-
/* catch overflow before it happens */
881-
if (numsignificantbytes > (PY_SSIZE_T_MAX - PyLong_SHIFT) / 8) {
882-
PyErr_SetString(PyExc_OverflowError,
883-
"byte array too long to convert to int");
884-
return NULL;
885-
}
886-
ndigits = (numsignificantbytes * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
877+
/* avoid integer overflow */
878+
ndigits = numsignificantbytes / PyLong_SHIFT * 8
879+
+ (numsignificantbytes % PyLong_SHIFT * 8 + PyLong_SHIFT - 1) / PyLong_SHIFT;
887880
v = _PyLong_New(ndigits);
888881
if (v == NULL)
889882
return NULL;

0 commit comments

Comments
 (0)