Skip to content

Commit 3939dba

Browse files
committed
MAINT: stats.rv_discrete: avoid infinite loops
1 parent a7015c6 commit 3939dba

File tree

3 files changed

+31
-12
lines changed

3 files changed

+31
-12
lines changed

scipy/stats/_distn_infrastructure.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,34 +3048,47 @@ def _drv2_ppfsingle(self, q, *args): # Use basic bisection algorithm
30483048
_a, _b = self._get_support(*args)
30493049
b = _b
30503050
a = _a
3051+
3052+
step = 10
30513053
if isinf(b): # Be sure ending point is > q
3052-
b = int(max(100*q, 10))
3054+
b = float(max(100*q, 10))
30533055
while 1:
30543056
if b >= _b:
30553057
qb = 1.0
30563058
break
30573059
qb = self._cdf(b, *args)
30583060
if (qb < q):
3059-
b += 10
3061+
b += step
3062+
step *= 2
30603063
else:
30613064
break
30623065
else:
30633066
qb = 1.0
3067+
3068+
step = 10
30643069
if isinf(a): # be sure starting point < q
3065-
a = int(min(-100*q, -10))
3070+
a = float(min(-100*q, -10))
30663071
while 1:
30673072
if a <= _a:
30683073
qb = 0.0
30693074
break
30703075
qa = self._cdf(a, *args)
30713076
if (qa > q):
3072-
a -= 10
3077+
a -= step
3078+
step *= 2
30733079
else:
30743080
break
30753081
else:
30763082
qa = self._cdf(a, *args)
30773083

3078-
while 1:
3084+
if np.isinf(a) or np.isinf(b):
3085+
message = "Arguments that bracket the requested quantile could not be found."
3086+
raise RuntimeError(message)
3087+
3088+
# maximum number of bisections within the normal numbers
3089+
# maxiter = int(np.log2(finfo.max) - np.log2(finfo.smallest_normal))
3090+
maxiter = 2046
3091+
for i in range(maxiter):
30793092
if (qa == q):
30803093
return a
30813094
if (qb == q):

scipy/stats/tests/test_discrete_distns.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
import itertools
33

4+
from scipy import stats
45
from scipy.stats import (betabinom, betanbinom, hypergeom, nhypergeom,
56
bernoulli, boltzmann, skellam, zipf, zipfian, binom,
67
nbinom, nchypergeom_fisher, nchypergeom_wallenius,
@@ -646,3 +647,15 @@ def test_gh20692(self):
646647
pmf = dist.pmf(k)
647648
pmf_k_int32 = dist.pmf(k_int32)
648649
assert_equal(pmf, pmf_k_int32)
650+
651+
652+
def test_gh20048():
653+
class test_dist_gen(stats.rv_discrete):
654+
def _cdf(self, k):
655+
return min(k / 100, 0.99)
656+
657+
test_dist = test_dist_gen(b=np.inf)
658+
659+
message = "Arguments that bracket..."
660+
with pytest.raises(RuntimeError, match=message):
661+
test_dist.ppf(0.999)

scipy/stats/tests/test_distributions.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,13 +2180,6 @@ def test_mean_small_p(self):
21802180
# 1.000000005
21812181
assert_allclose(m, 1.000000005)
21822182

2183-
def test_gh20048(self):
2184-
# gh-20048 reported an infinite loop; see if we can reproduce it
2185-
p, q = 0.25739239425774363, 0.9999999999999999
2186-
res = stats.logser(p).ppf(q)
2187-
ref = 99
2188-
assert_equal(res, ref)
2189-
21902183

21912184
class TestGumbel_r_l:
21922185
@pytest.fixture(scope='function')

0 commit comments

Comments
 (0)