1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11import math
12from decimal import Decimal
13from fractions import Fraction
14from typing import Literal, Optional, Union
15
16from hypothesis.control import reject
17from hypothesis.errors import InvalidArgument
18from hypothesis.internal.filtering import (
19 get_float_predicate_bounds,
20 get_integer_predicate_bounds,
21)
22from hypothesis.internal.floats import (
23 SMALLEST_SUBNORMAL,
24 float_of,
25 float_to_int,
26 int_to_float,
27 is_negative,
28 next_down,
29 next_down_normal,
30 next_up,
31 next_up_normal,
32 width_smallest_normals,
33)
34from hypothesis.internal.validation import (
35 check_type,
36 check_valid_bound,
37 check_valid_interval,
38)
39from hypothesis.strategies._internal.misc import nothing
40from hypothesis.strategies._internal.strategies import (
41 SampledFromStrategy,
42 SearchStrategy,
43)
44from hypothesis.strategies._internal.utils import cacheable, defines_strategy
45
46# See https://github.com/python/mypy/issues/3186 - numbers.Real is wrong!
47Real = Union[int, float, Fraction, Decimal]
48
49
50class IntegersStrategy(SearchStrategy):
51 def __init__(self, start, end):
52 assert isinstance(start, int) or start is None
53 assert isinstance(end, int) or end is None
54 assert start is None or end is None or start <= end
55 self.start = start
56 self.end = end
57
58 def __repr__(self):
59 if self.start is None and self.end is None:
60 return "integers()"
61 if self.end is None:
62 return f"integers(min_value={self.start})"
63 if self.start is None:
64 return f"integers(max_value={self.end})"
65 return f"integers({self.start}, {self.end})"
66
67 def do_draw(self, data):
68 # For bounded integers, make the bounds and near-bounds more likely.
69 forced = None
70 if (
71 self.end is not None
72 and self.start is not None
73 and self.end - self.start > 127
74 ):
75 bits = data.draw_integer(0, 127)
76 forced = {
77 122: self.start,
78 123: self.start,
79 124: self.end,
80 125: self.end,
81 126: self.start + 1,
82 127: self.end - 1,
83 }.get(bits)
84
85 return data.draw_integer(
86 min_value=self.start, max_value=self.end, forced=forced
87 )
88
89 def filter(self, condition):
90 if condition is math.isfinite:
91 return self
92 if condition in [math.isinf, math.isnan]:
93 return nothing()
94 kwargs, pred = get_integer_predicate_bounds(condition)
95
96 start, end = self.start, self.end
97 if "min_value" in kwargs:
98 start = max(kwargs["min_value"], -math.inf if start is None else start)
99 if "max_value" in kwargs:
100 end = min(kwargs["max_value"], math.inf if end is None else end)
101
102 if start != self.start or end != self.end:
103 if start is not None and end is not None and start > end:
104 return nothing()
105 self = type(self)(start, end)
106 if pred is None:
107 return self
108 return super().filter(pred)
109
110
111@cacheable
112@defines_strategy(force_reusable_values=True)
113def integers(
114 min_value: Optional[int] = None,
115 max_value: Optional[int] = None,
116) -> SearchStrategy[int]:
117 """Returns a strategy which generates integers.
118
119 If min_value is not None then all values will be >= min_value. If
120 max_value is not None then all values will be <= max_value
121
122 Examples from this strategy will shrink towards zero, and negative values
123 will also shrink towards positive (i.e. -n may be replaced by +n).
124 """
125 check_valid_bound(min_value, "min_value")
126 check_valid_bound(max_value, "max_value")
127 check_valid_interval(min_value, max_value, "min_value", "max_value")
128
129 if min_value is not None:
130 if min_value != int(min_value):
131 raise InvalidArgument(
132 "min_value=%r of type %r cannot be exactly represented as an integer."
133 % (min_value, type(min_value))
134 )
135 min_value = int(min_value)
136 if max_value is not None:
137 if max_value != int(max_value):
138 raise InvalidArgument(
139 "max_value=%r of type %r cannot be exactly represented as an integer."
140 % (max_value, type(max_value))
141 )
142 max_value = int(max_value)
143
144 return IntegersStrategy(min_value, max_value)
145
146
147class FloatStrategy(SearchStrategy):
148 """A strategy for floating point numbers."""
149
150 def __init__(
151 self,
152 *,
153 min_value: float,
154 max_value: float,
155 allow_nan: bool,
156 # The smallest nonzero number we can represent is usually a subnormal, but may
157 # be the smallest normal if we're running in unsafe denormals-are-zero mode.
158 # While that's usually an explicit error, we do need to handle the case where
159 # the user passes allow_subnormal=False.
160 smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL,
161 ):
162 super().__init__()
163 assert isinstance(allow_nan, bool)
164 assert smallest_nonzero_magnitude >= 0.0, "programmer error if this is negative"
165 if smallest_nonzero_magnitude == 0.0: # pragma: no cover
166 raise FloatingPointError(
167 "Got allow_subnormal=True, but we can't represent subnormal floats "
168 "right now, in violation of the IEEE-754 floating-point "
169 "specification. This is usually because something was compiled with "
170 "-ffast-math or a similar option, which sets global processor state. "
171 "See https://simonbyrne.github.io/notes/fastmath/ for a more detailed "
172 "writeup - and good luck!"
173 )
174 self.min_value = min_value
175 self.max_value = max_value
176 self.allow_nan = allow_nan
177 self.smallest_nonzero_magnitude = smallest_nonzero_magnitude
178
179 def __repr__(self):
180 return (
181 f"{self.__class__.__name__}({self.min_value=}, {self.max_value=}, "
182 f"{self.allow_nan=}, {self.smallest_nonzero_magnitude=})"
183 ).replace("self.", "")
184
185 def do_draw(self, data):
186 return data.draw_float(
187 min_value=self.min_value,
188 max_value=self.max_value,
189 allow_nan=self.allow_nan,
190 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude,
191 )
192
193 def filter(self, condition):
194 # Handle a few specific weird cases.
195 if condition is math.isfinite:
196 return FloatStrategy(
197 min_value=max(self.min_value, next_up(float("-inf"))),
198 max_value=min(self.max_value, next_down(float("inf"))),
199 allow_nan=False,
200 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude,
201 )
202 if condition is math.isinf:
203 if permitted_infs := [
204 x
205 for x in (-math.inf, math.inf)
206 if self.min_value <= x <= self.max_value
207 ]:
208 return SampledFromStrategy(permitted_infs)
209 return nothing()
210 if condition is math.isnan:
211 if not self.allow_nan:
212 return nothing()
213 return NanStrategy()
214
215 kwargs, pred = get_float_predicate_bounds(condition)
216 if not kwargs:
217 return super().filter(pred)
218 min_bound = max(kwargs.get("min_value", -math.inf), self.min_value)
219 max_bound = min(kwargs.get("max_value", math.inf), self.max_value)
220
221 # Adjustments for allow_subnormal=False, if any need to be made
222 if -self.smallest_nonzero_magnitude < min_bound < 0:
223 min_bound = -0.0
224 elif 0 < min_bound < self.smallest_nonzero_magnitude:
225 min_bound = self.smallest_nonzero_magnitude
226 if -self.smallest_nonzero_magnitude < max_bound < 0:
227 max_bound = -self.smallest_nonzero_magnitude
228 elif 0 < max_bound < self.smallest_nonzero_magnitude:
229 max_bound = 0.0
230
231 if min_bound > max_bound:
232 return nothing()
233 if (
234 min_bound > self.min_value
235 or self.max_value > max_bound
236 or (self.allow_nan and (-math.inf < min_bound or max_bound < math.inf))
237 ):
238 self = type(self)(
239 min_value=min_bound,
240 max_value=max_bound,
241 allow_nan=False,
242 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude,
243 )
244 if pred is None:
245 return self
246 return super().filter(pred)
247
248
249@cacheable
250@defines_strategy(force_reusable_values=True)
251def floats(
252 min_value: Optional[Real] = None,
253 max_value: Optional[Real] = None,
254 *,
255 allow_nan: Optional[bool] = None,
256 allow_infinity: Optional[bool] = None,
257 allow_subnormal: Optional[bool] = None,
258 width: Literal[16, 32, 64] = 64,
259 exclude_min: bool = False,
260 exclude_max: bool = False,
261) -> SearchStrategy[float]:
262 """Returns a strategy which generates floats.
263
264 - If min_value is not None, all values will be ``>= min_value``
265 (or ``> min_value`` if ``exclude_min``).
266 - If max_value is not None, all values will be ``<= max_value``
267 (or ``< max_value`` if ``exclude_max``).
268 - If min_value or max_value is not None, it is an error to enable
269 allow_nan.
270 - If both min_value and max_value are not None, it is an error to enable
271 allow_infinity.
272 - If inferred values range does not include subnormal values, it is an error
273 to enable allow_subnormal.
274
275 Where not explicitly ruled out by the bounds,
276 :wikipedia:`subnormals <Subnormal_number>`, infinities, and NaNs are possible
277 values generated by this strategy.
278
279 The width argument specifies the maximum number of bits of precision
280 required to represent the generated float. Valid values are 16, 32, or 64.
281 Passing ``width=32`` will still use the builtin 64-bit :class:`~python:float` class,
282 but always for values which can be exactly represented as a 32-bit float.
283
284 The exclude_min and exclude_max argument can be used to generate numbers
285 from open or half-open intervals, by excluding the respective endpoints.
286 Excluding either signed zero will also exclude the other.
287 Attempting to exclude an endpoint which is None will raise an error;
288 use ``allow_infinity=False`` to generate finite floats. You can however
289 use e.g. ``min_value=-math.inf, exclude_min=True`` to exclude only
290 one infinite endpoint.
291
292 Examples from this strategy have a complicated and hard to explain
293 shrinking behaviour, but it tries to improve "human readability". Finite
294 numbers will be preferred to infinity and infinity will be preferred to
295 NaN.
296 """
297 check_type(bool, exclude_min, "exclude_min")
298 check_type(bool, exclude_max, "exclude_max")
299
300 if allow_nan is None:
301 allow_nan = bool(min_value is None and max_value is None)
302 elif allow_nan and (min_value is not None or max_value is not None):
303 raise InvalidArgument(f"Cannot have {allow_nan=}, with min_value or max_value")
304
305 if width not in (16, 32, 64):
306 raise InvalidArgument(
307 f"Got {width=}, but the only valid values "
308 "are the integers 16, 32, and 64."
309 )
310
311 check_valid_bound(min_value, "min_value")
312 check_valid_bound(max_value, "max_value")
313
314 if math.copysign(1.0, -0.0) == 1.0: # pragma: no cover
315 raise FloatingPointError(
316 "Your Python install can't represent -0.0, which is required by the "
317 "IEEE-754 floating-point specification. This is probably because it was "
318 "compiled with an unsafe option like -ffast-math; for a more detailed "
319 "explanation see https://simonbyrne.github.io/notes/fastmath/"
320 )
321 if allow_subnormal and next_up(0.0, width=width) == 0: # pragma: no cover
322 # Not worth having separate CI envs and dependencies just to cover this branch;
323 # discussion in https://github.com/HypothesisWorks/hypothesis/issues/3092
324 #
325 # Erroring out here ensures that the database contents are interpreted
326 # consistently - which matters for such a foundational strategy, even if it's
327 # not always true for all user-composed strategies further up the stack.
328 from _hypothesis_ftz_detector import identify_ftz_culprits
329
330 try:
331 ftz_pkg = identify_ftz_culprits()
332 except Exception:
333 ftz_pkg = None
334 if ftz_pkg:
335 ftz_msg = (
336 f"This seems to be because the `{ftz_pkg}` package was compiled with "
337 f"-ffast-math or a similar option, which sets global processor state "
338 f"- see https://simonbyrne.github.io/notes/fastmath/ for details. "
339 f"If you don't know why {ftz_pkg} is installed, `pipdeptree -rp "
340 f"{ftz_pkg}` will show which packages depend on it."
341 )
342 else:
343 ftz_msg = (
344 "This is usually because something was compiled with -ffast-math "
345 "or a similar option, which sets global processor state. See "
346 "https://simonbyrne.github.io/notes/fastmath/ for a more detailed "
347 "writeup - and good luck!"
348 )
349 raise FloatingPointError(
350 f"Got {allow_subnormal=}, but we can't represent "
351 f"subnormal floats right now, in violation of the IEEE-754 floating-point "
352 f"specification. {ftz_msg}"
353 )
354
355 min_arg, max_arg = min_value, max_value
356 if min_value is not None:
357 min_value = float_of(min_value, width)
358 assert isinstance(min_value, float)
359 if max_value is not None:
360 max_value = float_of(max_value, width)
361 assert isinstance(max_value, float)
362
363 if min_value != min_arg:
364 raise InvalidArgument(
365 f"min_value={min_arg!r} cannot be exactly represented as a float "
366 f"of width {width} - use {min_value=} instead."
367 )
368 if max_value != max_arg:
369 raise InvalidArgument(
370 f"max_value={max_arg!r} cannot be exactly represented as a float "
371 f"of width {width} - use {max_value=} instead."
372 )
373
374 if exclude_min and (min_value is None or min_value == math.inf):
375 raise InvalidArgument(f"Cannot exclude {min_value=}")
376 if exclude_max and (max_value is None or max_value == -math.inf):
377 raise InvalidArgument(f"Cannot exclude {max_value=}")
378
379 assumed_allow_subnormal = allow_subnormal is None or allow_subnormal
380 if min_value is not None and (
381 exclude_min or (min_arg is not None and min_value < min_arg)
382 ):
383 min_value = next_up_normal(min_value, width, assumed_allow_subnormal)
384 if min_value == min_arg:
385 assert min_value == min_arg == 0
386 assert is_negative(min_arg)
387 assert not is_negative(min_value)
388 min_value = next_up_normal(min_value, width, assumed_allow_subnormal)
389 assert min_value > min_arg # type: ignore
390 if max_value is not None and (
391 exclude_max or (max_arg is not None and max_value > max_arg)
392 ):
393 max_value = next_down_normal(max_value, width, assumed_allow_subnormal)
394 if max_value == max_arg:
395 assert max_value == max_arg == 0
396 assert is_negative(max_value)
397 assert not is_negative(max_arg)
398 max_value = next_down_normal(max_value, width, assumed_allow_subnormal)
399 assert max_value < max_arg # type: ignore
400
401 if min_value == -math.inf:
402 min_value = None
403 if max_value == math.inf:
404 max_value = None
405
406 bad_zero_bounds = (
407 min_value == max_value == 0
408 and is_negative(max_value)
409 and not is_negative(min_value)
410 )
411 if (
412 min_value is not None
413 and max_value is not None
414 and (min_value > max_value or bad_zero_bounds)
415 ):
416 # This is a custom alternative to check_valid_interval, because we want
417 # to include the bit-width and exclusion information in the message.
418 msg = (
419 "There are no %s-bit floating-point values between min_value=%r "
420 "and max_value=%r" % (width, min_arg, max_arg)
421 )
422 if exclude_min or exclude_max:
423 msg += f", {exclude_min=} and {exclude_max=}"
424 raise InvalidArgument(msg)
425
426 if allow_infinity is None:
427 allow_infinity = bool(min_value is None or max_value is None)
428 elif allow_infinity:
429 if min_value is not None and max_value is not None:
430 raise InvalidArgument(
431 f"Cannot have {allow_infinity=}, with both min_value and max_value"
432 )
433 elif min_value == math.inf:
434 if min_arg == math.inf:
435 raise InvalidArgument("allow_infinity=False excludes min_value=inf")
436 raise InvalidArgument(
437 f"exclude_min=True turns min_value={min_arg!r} into inf, "
438 "but allow_infinity=False"
439 )
440 elif max_value == -math.inf:
441 if max_arg == -math.inf:
442 raise InvalidArgument("allow_infinity=False excludes max_value=-inf")
443 raise InvalidArgument(
444 f"exclude_max=True turns max_value={max_arg!r} into -inf, "
445 "but allow_infinity=False"
446 )
447
448 smallest_normal = width_smallest_normals[width]
449 if allow_subnormal is None:
450 if min_value is not None and max_value is not None:
451 if min_value == max_value:
452 allow_subnormal = -smallest_normal < min_value < smallest_normal
453 else:
454 allow_subnormal = (
455 min_value < smallest_normal and max_value > -smallest_normal
456 )
457 elif min_value is not None:
458 allow_subnormal = min_value < smallest_normal
459 elif max_value is not None:
460 allow_subnormal = max_value > -smallest_normal
461 else:
462 allow_subnormal = True
463 if allow_subnormal:
464 if min_value is not None and min_value >= smallest_normal:
465 raise InvalidArgument(
466 f"allow_subnormal=True, but minimum value {min_value} "
467 f"excludes values below float{width}'s "
468 f"smallest positive normal {smallest_normal}"
469 )
470 if max_value is not None and max_value <= -smallest_normal:
471 raise InvalidArgument(
472 f"allow_subnormal=True, but maximum value {max_value} "
473 f"excludes values above float{width}'s "
474 f"smallest negative normal {-smallest_normal}"
475 )
476
477 if min_value is None:
478 min_value = float("-inf")
479 if max_value is None:
480 max_value = float("inf")
481 if not allow_infinity:
482 min_value = max(min_value, next_up(float("-inf")))
483 max_value = min(max_value, next_down(float("inf")))
484 assert isinstance(min_value, float)
485 assert isinstance(max_value, float)
486 smallest_nonzero_magnitude = (
487 SMALLEST_SUBNORMAL if allow_subnormal else smallest_normal
488 )
489 result: SearchStrategy = FloatStrategy(
490 min_value=min_value,
491 max_value=max_value,
492 allow_nan=allow_nan,
493 smallest_nonzero_magnitude=smallest_nonzero_magnitude,
494 )
495
496 if width < 64:
497
498 def downcast(x):
499 try:
500 return float_of(x, width)
501 except OverflowError: # pragma: no cover
502 reject()
503
504 result = result.map(downcast)
505 return result
506
507
508class NanStrategy(SearchStrategy):
509 """Strategy for sampling the space of nan float values."""
510
511 def do_draw(self, data):
512 # Nans must have all exponent bits and the first mantissa bit set, so
513 # we generate by taking 64 random bits and setting the required ones.
514 sign_bit = int(data.draw_boolean()) << 63
515 nan_bits = float_to_int(math.nan)
516 mantissa_bits = data.draw_integer(0, 2**52 - 1)
517 return int_to_float(sign_bit | nan_bits | mantissa_bits)