Repository: HypothesisWorks/hypothesis

Issue:
 The floats strategy with allow_infinite set to False returns infinity if min_value is large
Similar to issue #1476, when `allow_infinity` is set to `False` in conjunction with `min_value` set to a large value, infinity still can be returned. 

The following code reproduces the error for on my computer, but I think the `min_value` may have to change depending on the system being used. 
```python
from hypothesis.strategies import floats
from hypothesis.errors import NoExamples
import math

strategy = floats(min_value=1e+302, allow_infinity=False, allow_nan=False).filter(lambda x: math.isinf(x))

try:
    strategy.example()
except NoExamples:
    print("Expected result")
else:
    raise Exception("Strategy returned infinity")
```

I think the culprit is [this map](https://github.com/HypothesisWorks/hypothesis/blob/master/hypothesis-python/src/hypothesis/strategies.py#L535).  I believe according to that code any positive `min_value` could result in infinity being returned, it is just more likely the larger the value provided. 

A workaround is adding a `.filter(lambda x: not math.isinf(x))` to any strategy. 
Incorrect error message in floats strategy
For the floats strategy, if `min_value` or `max_value` can not be represented by the `width`, one of these [error messages](https://github.com/HypothesisWorks/hypothesis/blob/master/hypothesis-python/src/hypothesis/strategies.py#L536&#L547) is raised. The issue is that the error message format variables are in the wrong order. It should be `(min_arg, width, min_value)` instead of `(min_value, width, min_arg)`. The same is true for the `max_value` error message.




Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/_strategies.py b/hypothesis-python/src/hypothesis/_strategies.py
--- a/hypothesis-python/src/hypothesis/_strategies.py
+++ b/hypothesis-python/src/hypothesis/_strategies.py
@@ -33,7 +33,7 @@
 import attr
 
 from hypothesis._settings import note_deprecation
-from hypothesis.control import assume, cleanup, current_build_context, note, reject
+from hypothesis.control import cleanup, current_build_context, note, reject
 from hypothesis.errors import InvalidArgument, ResolutionFailed
 from hypothesis.internal.cache import LRUReusedCache
 from hypothesis.internal.cathetus import cathetus
@@ -497,14 +497,14 @@ def floats(
         note_deprecation(
             "min_value=%r cannot be exactly represented as a float of width "
             "%d, which will be an error in a future version. Use min_value=%r "
-            "instead." % (min_value, width, min_arg),
+            "instead." % (min_arg, width, min_value),
             since="2018-10-10",
         )
     if max_value != max_arg:
         note_deprecation(
             "max_value=%r cannot be exactly represented as a float of width "
             "%d, which will be an error in a future version. Use max_value=%r "
-            "instead" % (max_value, width, max_arg),
+            "instead" % (max_arg, width, max_value),
             since="2018-10-10",
         )
 
@@ -534,6 +534,10 @@ def floats(
                 "Cannot have allow_infinity=%r, with both min_value and "
                 "max_value" % (allow_infinity)
             )
+    elif min_value == float("inf"):
+        raise InvalidArgument("allow_infinity=False excludes min_value=inf")
+    elif max_value == float("-inf"):
+        raise InvalidArgument("allow_infinity=False excludes max_value=-inf")
 
     if min_value is None and max_value is None:
         result = FloatStrategy(
@@ -571,9 +575,10 @@ def floats(
             )
         else:
             result = floats(allow_infinity=allow_infinity, allow_nan=False).map(
-                lambda x: assume(not math.isnan(x))
-                and min_value + abs(x)  # type: ignore
+                lambda x: min_value + abs(x)  # type: ignore
             )
+            if not allow_infinity:
+                result = result.filter(lambda x: not math.isinf(x))
         if min_value == 0 and not is_negative(min_value):
             result = result.filter(lambda x: math.copysign(1.0, x) == 1)
     else:
@@ -584,9 +589,10 @@ def floats(
             )
         else:
             result = floats(allow_infinity=allow_infinity, allow_nan=False).map(
-                lambda x: assume(not math.isnan(x))
-                and max_value - abs(x)  # type: ignore
+                lambda x: max_value - abs(x)  # type: ignore
             )
+            if not allow_infinity:
+                result = result.filter(lambda x: not math.isinf(x))
         if max_value == 0 and is_negative(max_value):
             result = result.filter(is_negative)
 
diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/floats.py b/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
--- a/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
+++ b/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
@@ -76,7 +76,7 @@
     1. If the unbiased exponent is <= 0 we reverse it bitwise.
     2. If the unbiased exponent is >= 52 we leave it alone.
     3. If the unbiased exponent is in the range [1, 51] then we reverse the
-       low k bits, where k is 52 - unbiased exponen.
+       low k bits, where k is 52 - unbiased exponent.
 
 The low bits correspond to the fractional part of the floating point number.
 Reversing it bitwise means that we try to minimize the low bits, which kills
diff --git a/hypothesis-python/src/hypothesis/searchstrategy/numbers.py b/hypothesis-python/src/hypothesis/searchstrategy/numbers.py
--- a/hypothesis-python/src/hypothesis/searchstrategy/numbers.py
+++ b/hypothesis-python/src/hypothesis/searchstrategy/numbers.py
@@ -108,7 +108,9 @@ def __init__(self, allow_infinity, allow_nan):
         self.sampler = d.Sampler(weights)
 
     def __repr__(self):
-        return "%s()" % (self.__class__.__name__,)
+        return "{}(allow_infinity={}, allow_nan={})".format(
+            self.__class__.__name__, self.allow_infinity, self.allow_nan
+        )
 
     def permitted(self, f):
         assert isinstance(f, float)


Test patch:
diff --git a/hypothesis-python/tests/cover/test_direct_strategies.py b/hypothesis-python/tests/cover/test_direct_strategies.py
--- a/hypothesis-python/tests/cover/test_direct_strategies.py
+++ b/hypothesis-python/tests/cover/test_direct_strategies.py
@@ -120,6 +120,8 @@ def fn_ktest(*fnkwargs):
     (ds.floats, {"min_value": 0.0, "allow_nan": True}),
     (ds.floats, {"max_value": 0.0, "allow_nan": True}),
     (ds.floats, {"min_value": 0.0, "max_value": 1.0, "allow_infinity": True}),
+    (ds.floats, {"min_value": float("inf"), "allow_infinity": False}),
+    (ds.floats, {"max_value": float("-inf"), "allow_infinity": False}),
     (ds.complex_numbers, {"min_magnitude": float("nan")}),
     (ds.complex_numbers, {"max_magnitude": float("nan")}),
     (ds.complex_numbers, {"max_magnitude": complex(1, 2)}),
@@ -190,6 +192,8 @@ def test_validates_keyword_arguments(fn, kwargs):
     (ds.floats, {}),
     (ds.floats, {"min_value": 1.0}),
     (ds.floats, {"max_value": 1.0}),
+    (ds.floats, {"min_value": float("inf")}),
+    (ds.floats, {"max_value": float("-inf")}),
     (ds.floats, {"max_value": 1.0, "min_value": -1.0}),
     (ds.floats, {"max_value": 1.0, "min_value": -1.0, "allow_infinity": False}),
     (ds.floats, {"min_value": 1.0, "allow_nan": False}),
diff --git a/hypothesis-python/tests/cover/test_float_nastiness.py b/hypothesis-python/tests/cover/test_float_nastiness.py
--- a/hypothesis-python/tests/cover/test_float_nastiness.py
+++ b/hypothesis-python/tests/cover/test_float_nastiness.py
@@ -251,3 +251,15 @@ def test_no_single_floats_in_range():
         with warnings.catch_warnings():
             warnings.simplefilter("ignore")
             st.floats(low, high, width=32).validate()
+
+
+# If the floats() strategy adds random floats to a value as large as 10^304
+# without handling overflow, we are very likely to generate infinity.
+@given(st.floats(min_value=1e304, allow_infinity=False))
+def test_finite_min_bound_does_not_overflow(x):
+    assert not math.isinf(x)
+
+
+@given(st.floats(max_value=-1e304, allow_infinity=False))
+def test_finite_max_bound_does_not_overflow(x):
+    assert not math.isinf(x)