From 7411a8206e94ba23cf111ebf069a657681c9df5e Mon Sep 17 00:00:00 2001 From: Zac-HD Date: Tue, 16 Nov 2021 23:07:22 +1100 Subject: [PATCH] Generate subnormal floats --- hypothesis-python/RELEASE.rst | 8 +++++++ .../strategies/_internal/numbers.py | 8 +++++-- .../tests/nocover/test_floating.py | 24 +++++++++---------- 3 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 hypothesis-python/RELEASE.rst diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..3da656b61b --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,8 @@ +RELEASE_TYPE: patch + +This patch makes :func:`hypothesis.strategies.floats` generate +:wikipedia:`"subnormal" floating point numbers ` +more often, as these rare values can have strange interactions with +`unsafe compiler optimisations like -ffast-math +`__ +(:issue:`2976`). diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py b/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py index 43c0d8bc95..328a94bc9b 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/numbers.py @@ -17,6 +17,7 @@ import operator from decimal import Decimal from fractions import Fraction +from sys import float_info from typing import Optional, Union from hypothesis.control import assume, reject @@ -157,8 +158,9 @@ def integers( 10e6, 10e-6, 1.175494351e-38, - 2.2250738585072014e-308, - 1.7976931348623157e308, + next_up(0.0), + float_info.min, + float_info.max, 3.402823466e38, 9007199254740992, 1 - 10e-6, @@ -166,6 +168,8 @@ def integers( 1.192092896e-07, 2.2204460492503131e-016, ] + + [2.0 ** -n for n in (24, 14, 149, 126)] # minimum (sub)normals for float16,32 + + [float_info.min / n for n in (2, 10, 1000, 100_000)] # subnormal in float64 + [math.inf, math.nan] * 5, key=flt.float_to_lex, ) diff --git a/hypothesis-python/tests/nocover/test_floating.py b/hypothesis-python/tests/nocover/test_floating.py index bafaa57517..a84c9dba8a 100644 --- a/hypothesis-python/tests/nocover/test_floating.py +++ b/hypothesis-python/tests/nocover/test_floating.py @@ -21,6 +21,7 @@ import pytest from hypothesis import HealthCheck, assume, given, settings +from hypothesis.internal.floats import next_down from hypothesis.strategies import data, floats, lists from tests.common.utils import fails @@ -105,35 +106,34 @@ def test_is_in_exact_int_range(x): # Tests whether we can represent subnormal floating point numbers. -# This is essentially a function of how the python interpreter -# was compiled. +# IEE-754 requires subnormal support, but it's often disabled anyway by unsafe +# compiler options like `-ffast-math`. On most hardware that's even a global +# config option, so *linking against* something built this way can break us. # Everything is terrible -if math.ldexp(0.25, -1022) > 0: - REALLY_SMALL_FLOAT = sys.float_info.min -else: - REALLY_SMALL_FLOAT = sys.float_info.min * 2 +FLUSH_SUBNORMALS_TO_ZERO = next_down(sys.float_info.min) == 0.0 -# These two tests have been failing for an unknown amount of time, but that -# failure was previously being masked by a bug in our `@fails` decorator. +def test_compiled_with_sane_math_options(): + # Checks that we're not unexpectedly skipping the subnormal tests below. + assert not FLUSH_SUBNORMALS_TO_ZERO -@pytest.mark.xfail +@pytest.mark.skipif(FLUSH_SUBNORMALS_TO_ZERO, reason="broken by unsafe compiler flags") @fails @given(floats()) @TRY_HARDER def test_can_generate_really_small_positive_floats(x): assume(x > 0) - assert x >= REALLY_SMALL_FLOAT + assert x >= sys.float_info.min -@pytest.mark.xfail +@pytest.mark.skipif(FLUSH_SUBNORMALS_TO_ZERO, reason="broken by unsafe compiler flags") @fails @given(floats()) @TRY_HARDER def test_can_generate_really_small_negative_floats(x): assume(x < 0) - assert x <= -REALLY_SMALL_FLOAT + assert x <= -sys.float_info.min @fails