Skip to content
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

bpo-40282: Allow random.getrandbits(0) #19539

Merged
merged 7 commits into from Apr 17, 2020

Conversation

pitrou
Copy link
Member

@pitrou pitrou commented Apr 15, 2020

cum |= v
cpl_cum |= all_bits ^ v
self.assertEqual(cum, all_bits)
self.assertEqual(cpl_cum, all_bits)
Copy link
Member Author

@pitrou pitrou Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note I've enhanced this test a bit to check that all bits take both values 0 and 1 accross 100 draws.

Doc/library/random.rst Outdated Show resolved Hide resolved
Lib/random.py Outdated Show resolved Hide resolved
Lib/random.py Outdated Show resolved Hide resolved
Lib/random.py Outdated Show resolved Hide resolved
@@ -261,6 +261,8 @@ def randint(self, a, b):
def _randbelow_with_getrandbits(self, n):
"Return a random int in the range [0,n). Raises ValueError if n==0."

if n == 0:
raise ValueError("Boundary cannot be zero")
getrandbits = self.getrandbits
k = n.bit_length() # don't use (n-1) here because n can be 1
Copy link
Member

@serhiy-storchaka serhiy-storchaka Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use (n-1) now.

Suggested change
k = n.bit_length() # don't use (n-1) here because n can be 1
k = (n-1).bit_length()

Copy link
Member Author

@pitrou pitrou Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to ensure that the values produced for a given seed don't change, so we probably can't change this anymore.

Lib/random.py Outdated
if n == 0:
raise ValueError("Boundary cannot be zero")
Copy link
Member

@serhiy-storchaka serhiy-storchaka Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use assert?

Copy link
Member Author

@pitrou pitrou Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same error as in the other implementation (the one without getrandbits).

Copy link
Member

@serhiy-storchaka serhiy-storchaka Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this check causes a performance regression.

Copy link
Member

@aeros aeros Apr 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any situation where a user could specifically encounter the above ValueError from the public methods? If not, I think an assertion would make more sense here; seeing as it's a private member that's only being used from within the random module. IMO, the same applies to _randbelow_without_getrandbits().

Also, how significant is the performance regression @serhiy-storchaka?

Modules/_randommodule.c Outdated Show resolved Hide resolved
@rhettinger
Copy link
Contributor

rhettinger commented Apr 15, 2020

This makes shuffle() about 6% slower:

python -m timeit -r11 -s 'from random import shuffle' -s 's=list(range(1000))' 'shuffle(s)'

Copy link
Member

@vstinner vstinner left a comment

random.Random has a surprising API. The base class is Mersenne Twister which implements getrandbits(). Inheritance is non-trivial. Does your change work if a subclass implements _randbelow()? What if it implements random()? See Random.__init_subclass__().

Lib/test/test_random.py Outdated Show resolved Hide resolved
Doc/library/random.rst Show resolved Hide resolved
Doc/library/random.rst Outdated Show resolved Hide resolved
@serhiy-storchaka
Copy link
Member

serhiy-storchaka commented Apr 15, 2020

_randbelow() is not designed for overriding. If there is a problem with this, it can be made private (__randbelow()).

If the subclass implements random() but not getrandbits(), its methods will use random() instead of getrandbits().

Lib/random.py Outdated Show resolved Hide resolved
@pitrou pitrou force-pushed the bpo40282-getrandbits0 branch from 9ccb7bc to 3b86eb8 Compare Apr 17, 2020
@pitrou
Copy link
Member Author

pitrou commented Apr 17, 2020

I rebased and applied two trivial changes from reviewing. Will merge if CI is green. Thanks everyone!

@pitrou pitrou force-pushed the bpo40282-getrandbits0 branch from 3b86eb8 to c4673f0 Compare Apr 17, 2020
@pitrou pitrou merged commit 75a3378 into python:master Apr 17, 2020
@pitrou pitrou deleted the bpo40282-getrandbits0 branch Apr 17, 2020
@vstinner
Copy link
Member

vstinner commented Apr 20, 2020

random.Random has a surprising API. The base class is Mersenne Twister which implements getrandbits(). Inheritance is non-trivial. Does your change work if a subclass implements _randbelow()? What if it implements random()? See Random.init_subclass().

I created bpo-40346: "Redesign random.Random class inheritance".

CuriousLearner added a commit to CuriousLearner/cpython that referenced this pull request May 29, 2020
* master: (1985 commits)
  bpo-40179: Fix translation of #elif in Argument Clinic (pythonGH-19364)
  bpo-35967: Skip test with `uname -p` on Android (pythonGH-19577)
  bpo-40257: Improve help for the typing module (pythonGH-19546)
  Fix two typos in multiprocessing (pythonGH-19571)
  bpo-40286: Use random.randbytes() in tests (pythonGH-19575)
  bpo-40286: Makes simpler the relation between randbytes() and getrandbits() (pythonGH-19574)
  bpo-39894: Route calls from pathlib.Path.samefile() to os.stat() via the path accessor (pythonGH-18836)
  bpo-39897: Remove needless `Path(self.parent)` call, which makes `is_mount()` misbehave in `Path` subclasses. (pythonGH-18839)
  bpo-40282: Allow random.getrandbits(0) (pythonGH-19539)
  bpo-40302: UTF-32 encoder SWAB4() macro use a|b rather than a+b (pythonGH-19572)
  bpo-40302: Replace PY_INT64_T with int64_t (pythonGH-19573)
  bpo-40286: Add randbytes() method to random.Random (pythonGH-19527)
  bpo-39901: Move `pathlib.Path.owner()` and `group()` implementations into the path accessor. (pythonGH-18844)
  bpo-40300: Allow empty logging.Formatter.default_msec_format. (pythonGH-19551)
  bpo-40302: Add pycore_byteswap.h header file (pythonGH-19552)
  bpo-40287: Fix SpooledTemporaryFile.seek() return value (pythonGH-19540)
  Minor modernization and readability improvement to the tokenizer example (pythonGH-19558)
  bpo-40294: Fix _asyncio when module is loaded/unloaded multiple times (pythonGH-19542)
  Fix parameter names in assertIn() docs (pythonGH-18829)
  bpo-39793: use the same domain on make_msgid tests (python#18698)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants