Skip to content

[3.14] gh-133489: Remove size restrictions on getrandbits() and randbytes() (GH-133658) #134964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Lib/test/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ def test_getrandbits(self):
self.assertRaises(TypeError, self.gen.getrandbits)
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
self.assertRaises(ValueError, self.gen.getrandbits, -1)
self.assertRaises(OverflowError, self.gen.getrandbits, 1<<1000)
self.assertRaises(ValueError, self.gen.getrandbits, -1<<1000)
self.assertRaises(TypeError, self.gen.getrandbits, 10.1)

def test_pickling(self):
Expand Down Expand Up @@ -435,6 +437,8 @@ def test_randbytes(self):
self.assertRaises(TypeError, self.gen.randbytes)
self.assertRaises(TypeError, self.gen.randbytes, 1, 2)
self.assertRaises(ValueError, self.gen.randbytes, -1)
self.assertRaises(OverflowError, self.gen.randbytes, 1<<1000)
self.assertRaises((ValueError, OverflowError), self.gen.randbytes, -1<<1000)
self.assertRaises(TypeError, self.gen.randbytes, 1.0)

def test_mu_sigma_default_args(self):
Expand Down Expand Up @@ -806,6 +810,22 @@ def test_getrandbits(self):
self.assertEqual(self.gen.getrandbits(100),
97904845777343510404718956115)

def test_getrandbits_2G_bits(self):
size = 2**31
self.gen.seed(1234567)
x = self.gen.getrandbits(size)
self.assertEqual(x.bit_length(), size)
self.assertEqual(x & (2**100-1), 890186470919986886340158459475)
self.assertEqual(x >> (size-100), 1226514312032729439655761284440)

@support.bigmemtest(size=2**32, memuse=1/8+2/15, dry_run=False)
def test_getrandbits_4G_bits(self, size):
self.gen.seed(1234568)
x = self.gen.getrandbits(size)
self.assertEqual(x.bit_length(), size)
self.assertEqual(x & (2**100-1), 287241425661104632871036099814)
self.assertEqual(x >> (size-100), 739728759900339699429794460738)

def test_randrange_uses_getrandbits(self):
# Verify use of getrandbits by randrange
# Use same seed as in the cross-platform repeatability test
Expand Down Expand Up @@ -962,6 +982,14 @@ def test_randbytes_getrandbits(self):
self.assertEqual(self.gen.randbytes(n),
gen2.getrandbits(n * 8).to_bytes(n, 'little'))

@support.bigmemtest(size=2**29, memuse=1+16/15, dry_run=False)
def test_randbytes_256M(self, size):
self.gen.seed(2849427419)
x = self.gen.randbytes(size)
self.assertEqual(len(x), size)
self.assertEqual(x[:12].hex(), 'f6fd9ae63855ab91ea238b4f')
self.assertEqual(x[-12:].hex(), '0e7af69a84ee99bf4a11becc')

def test_sample_counts_equivalence(self):
# Test the documented strong equivalence to a sample with repeated elements.
# We run this test on random.Random() which makes deterministic selections
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`random.getrandbits` can now generate more that 2\ :sup:`31` bits.
:func:`random.randbytes` can now generate more that 256 MiB.
20 changes: 9 additions & 11 deletions Modules/_randommodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -497,34 +497,32 @@ _random_Random_setstate_impl(RandomObject *self, PyObject *state)
_random.Random.getrandbits
self: self(type="RandomObject *")
k: int
k: uint64
/
getrandbits(k) -> x. Generates an int with k random bits.
[clinic start generated code]*/

static PyObject *
_random_Random_getrandbits_impl(RandomObject *self, int k)
/*[clinic end generated code: output=b402f82a2158887f input=87603cd60f79f730]*/
_random_Random_getrandbits_impl(RandomObject *self, uint64_t k)
/*[clinic end generated code: output=c30ef8435f3433cf input=64226ac13bb4d2a3]*/
{
int i, words;
Py_ssize_t i, words;
uint32_t r;
uint32_t *wordarray;
PyObject *result;

if (k < 0) {
PyErr_SetString(PyExc_ValueError,
"number of bits must be non-negative");
return NULL;
}

if (k == 0)
return PyLong_FromLong(0);

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

words = (k - 1) / 32 + 1;
if ((k - 1u) / 32u + 1u > PY_SSIZE_T_MAX / 4u) {
PyErr_NoMemory();
return NULL;
}
words = (k - 1u) / 32u + 1u;
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
if (wordarray == NULL) {
PyErr_NoMemory();
Expand Down
10 changes: 5 additions & 5 deletions Modules/clinic/_randommodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 3 additions & 10 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -971,16 +971,9 @@ _PyLong_FromByteArray(const unsigned char* bytes, size_t n,
++numsignificantbytes;
}

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