From 808d22be0a7e60630ff7cf06f850f666bc0a1359 Mon Sep 17 00:00:00 2001 From: Leonardo De Marchi Date: Sat, 3 Jun 2017 21:29:11 +0100 Subject: [PATCH 1/4] Sync-up expovariate() and gammavariate() code --- Lib/random.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Lib/random.py b/Lib/random.py index ad1c9167b02a65c..286811bdd9a8d5f 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -539,11 +539,8 @@ def gammavariate(self, alpha, beta): return x * beta elif alpha == 1.0: - # expovariate(1) - u = random() - while u <= 1e-7: - u = random() - return -_log(u) * beta + # expovariate(1.0 / beta) + return -_log(1.0 - random()) * beta else: # alpha is between 0 and 1 (exclusive) From 986cac1bd8df62e0d1af3658a0023ecc9ecb1c56 Mon Sep 17 00:00:00 2001 From: Leonardo De Marchi Date: Thu, 8 Jun 2017 13:59:43 +0100 Subject: [PATCH 2/4] bpo-30561: fix the tests for gammavariate refactor --- Lib/test/test_random.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 45468c7ce490416..8734915abea37ae 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -790,12 +790,13 @@ def test_gammavariate_errors(self): self.assertRaises(ValueError, random.gammavariate, 2, 0) self.assertRaises(ValueError, random.gammavariate, 1, -3) + + # There are three different possibilities in the current implementation + # of random.gammavariate(), depending on the value of 'alpha'. What we + # are going to do here is to fix the values returned by random() to + # generate test cases that provide 100% line coverage of the method. @unittest.mock.patch('random.Random.random') - def test_gammavariate_full_code_coverage(self, random_mock): - # There are three different possibilities in the current implementation - # of random.gammavariate(), depending on the value of 'alpha'. What we - # are going to do here is to fix the values returned by random() to - # generate test cases that provide 100% line coverage of the method. + def test_gammavariate_alpha_greater_one(self, random_mock): # #1: alpha > 1.0: we want the first random number to be outside the # [1e-7, .9999999] range, so that the continue statement executes @@ -804,12 +805,18 @@ def test_gammavariate_full_code_coverage(self, random_mock): returned_value = random.gammavariate(1.1, 2.3) self.assertAlmostEqual(returned_value, 2.53) - # #2: alpha == 1: first random number less than 1e-7 to that the body + @unittest.mock.patch('random.Random.random') + def test_gammavariate_alpha_equal_one(self, random_mock): + + # #2: alpha == 1: first random number less than 1e-7. The execution body # of the while loop executes once. Then random.random() returns 0.45, # which causes while to stop looping and the algorithm to terminate. - random_mock.side_effect = [1e-8, 0.45] + random_mock.side_effect = [0.45] returned_value = random.gammavariate(1.0, 3.14) - self.assertAlmostEqual(returned_value, 2.507314166123803) + self.assertAlmostEqual(returned_value, 1.877208182372648) + + @unittest.mock.patch('random.Random.random') + def test_gammavariate_alpha_between_zero_and_one(self, random_mock): # #3: 0 < alpha < 1. This is the most complex region of code to cover, # as there are multiple if-else statements. Let's take a look at the From ee13eff91aeb1ce8a51175797912ed5901524424 Mon Sep 17 00:00:00 2001 From: Leonardo De Marchi Date: Thu, 8 Jun 2017 14:04:29 +0100 Subject: [PATCH 3/4] bpo-30561: add test for gammavariate and update tests description --- Lib/test/test_random.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 8734915abea37ae..f94537b3f16fe08 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -790,7 +790,6 @@ def test_gammavariate_errors(self): self.assertRaises(ValueError, random.gammavariate, 2, 0) self.assertRaises(ValueError, random.gammavariate, 1, -3) - # There are three different possibilities in the current implementation # of random.gammavariate(), depending on the value of 'alpha'. What we # are going to do here is to fix the values returned by random() to @@ -798,7 +797,8 @@ def test_gammavariate_errors(self): @unittest.mock.patch('random.Random.random') def test_gammavariate_alpha_greater_one(self, random_mock): - # #1: alpha > 1.0: we want the first random number to be outside the + # #1: alpha > 1.0. + # We want the first random number to be outside the # [1e-7, .9999999] range, so that the continue statement executes # once. The values of u1 and u2 will be 0.5 and 0.3, respectively. random_mock.side_effect = [1e-8, 0.5, 0.3] @@ -808,17 +808,30 @@ def test_gammavariate_alpha_greater_one(self, random_mock): @unittest.mock.patch('random.Random.random') def test_gammavariate_alpha_equal_one(self, random_mock): - # #2: alpha == 1: first random number less than 1e-7. The execution body - # of the while loop executes once. Then random.random() returns 0.45, + # #2.a: alpha == 1. + # The execution body of the while loop executes once. + # Then random.random() returns 0.45, # which causes while to stop looping and the algorithm to terminate. random_mock.side_effect = [0.45] returned_value = random.gammavariate(1.0, 3.14) self.assertAlmostEqual(returned_value, 1.877208182372648) + @unittest.mock.patch('random.Random.random') + def test_gammavariate_alpha_equal_one_equals_expovariate(self, random_mock): + + # #2.b: alpha == 1. + # It must be equivalent of calling expovariate(1.0 / beta). + beta = 3.14 + random_mock.side_effect = [1e-8, 1e-8] + gammavariate_returned_value = random.gammavariate(1.0, beta) + expovariate_returned_value = random.expovariate(1.0 / beta) + self.assertAlmostEqual(gammavariate_returned_value, expovariate_returned_value) + @unittest.mock.patch('random.Random.random') def test_gammavariate_alpha_between_zero_and_one(self, random_mock): - # #3: 0 < alpha < 1. This is the most complex region of code to cover, + # #3: 0 < alpha < 1. + # This is the most complex region of code to cover, # as there are multiple if-else statements. Let's take a look at the # source code, and determine the values that we need accordingly: # From 3220c79421439475288f18c226c0c14162970e4a Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sun, 23 Dec 2018 22:28:14 -0800 Subject: [PATCH 4/4] Add NEWS blurb --- .../next/Library/2018-12-23-22-27-59.bpo-30561.PSRQ2w.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-12-23-22-27-59.bpo-30561.PSRQ2w.rst diff --git a/Misc/NEWS.d/next/Library/2018-12-23-22-27-59.bpo-30561.PSRQ2w.rst b/Misc/NEWS.d/next/Library/2018-12-23-22-27-59.bpo-30561.PSRQ2w.rst new file mode 100644 index 000000000000000..ae99b7cb0ae0591 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-23-22-27-59.bpo-30561.PSRQ2w.rst @@ -0,0 +1,4 @@ +random.gammavariate(1.0, beta) now computes the same result as +random.expovariate(1.0 / beta). This synchonizes the two algorithms and +eliminates some idiosyncrasies in the old implementation. It does however +produce a difference stream of random variables than it used to.