[Python-checkins] Add recipe for a version of random() with a larger population (GH-22664)

Raymond Hettinger webhook-mailer at python.org
Tue Oct 13 14:54:31 EDT 2020


https://github.com/python/cpython/commit/8b2ff4c03d150c43df3e8438d323b7f7bfe3353c
commit: 8b2ff4c03d150c43df3e8438d323b7f7bfe3353c
branch: master
author: Raymond Hettinger <rhettinger at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2020-10-13T11:54:21-07:00
summary:

Add recipe for a version of random() with a larger population (GH-22664)

files:
M Doc/library/random.rst

diff --git a/Doc/library/random.rst b/Doc/library/random.rst
index af5131df280c2..c19a8e0a7cb5b 100644
--- a/Doc/library/random.rst
+++ b/Doc/library/random.rst
@@ -391,8 +391,8 @@ change across Python versions, but two aspects are guaranteed not to change:
 
 .. _random-examples:
 
-Examples and Recipes
---------------------
+Examples
+--------
 
 Basic examples::
 
@@ -516,6 +516,52 @@ Simulation of arrival times and service deliveries for a multiserver queue::
     print(f'Mean wait: {mean(waits):.1f}   Max wait: {max(waits):.1f}')
     print('Quartiles:', [round(q, 1) for q in quantiles(waits)])
 
+Recipes
+-------
+
+The default :func:`.random` returns multiples of 2⁻⁵³ in the range
+*0.0 ≤ x < 1.0*.  All such numbers are evenly spaced and exactly
+representable as Python floats.  However, many floats in that interval
+are not possible selections.  For example, ``0.05954861408025609``
+isn't an integer multiple of 2⁻⁵³.
+
+The following recipe takes a different approach.  All floats in the
+interval are possible selections.  Conceptually it works by choosing
+from evenly spaced multiples of 2⁻¹⁰⁷⁴ and then rounding down to the
+nearest representable float.
+
+For efficiency, the actual mechanics involve calling
+:func:`~math.ldexp` to construct a representable float.  The mantissa
+comes from a uniform distribution of integers in the range *2⁵² ≤
+mantissa < 2⁵³*.  The exponent comes from a geometric distribution
+where exponents smaller than *-53* occur half as often as the next
+larger exponent.
+
+::
+
+    from random import Random
+    from math import ldexp
+
+    class FullRandom(Random):
+
+        def random(self):
+            mantissa = 0x10_0000_0000_0000 | self.getrandbits(52)
+            exponent = -53
+            x = 0
+            while not x:
+                x = self.getrandbits(32)
+                exponent += x.bit_length() - 32
+            return ldexp(mantissa, exponent)
+
+All of the real valued distributions will use the new method::
+
+    >>> fr = FullRandom()
+    >>> fr.random()
+    0.05954861408025609
+    >>> fr.expovariate(0.25)
+    8.87925541791544
+
+
 .. seealso::
 
    `Statistics for Hackers <https://www.youtube.com/watch?v=Iq9DzN6mvYA>`_
@@ -536,3 +582,8 @@ Simulation of arrival times and service deliveries for a multiserver queue::
    a tutorial by `Peter Norvig <http://norvig.com/bio.html>`_ covering
    the basics of probability theory, how to write simulations, and
    how to perform data analysis using Python.
+
+   `Generating Pseudo-random Floating-Point Values
+   <https://allendowney.com/research/rand/downey07randfloat.pdf>`_ a
+   paper by Allen B. Downey describing ways to generate more
+   fine-grained floats than normally generated by :func:`.random`.



More information about the Python-checkins mailing list