numbers.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # coding=utf-8
  2. #
  3. # This file is part of Hypothesis, which may be found at
  4. # https://github.com/HypothesisWorks/hypothesis-python
  5. #
  6. # Most of this work is copyright (C) 2013-2018 David R. MacIver
  7. # (david@drmaciver.com), but it contains contributions by others. See
  8. # CONTRIBUTING.rst for a full list of people who may hold copyright, and
  9. # consult the git log if you need to determine who owns an individual
  10. # contribution.
  11. #
  12. # This Source Code Form is subject to the terms of the Mozilla Public License,
  13. # v. 2.0. If a copy of the MPL was not distributed with this file, You can
  14. # obtain one at http://mozilla.org/MPL/2.0/.
  15. #
  16. # END HEADER
  17. from __future__ import division, print_function, absolute_import
  18. import math
  19. import hypothesis.internal.conjecture.utils as d
  20. import hypothesis.internal.conjecture.floats as flt
  21. from hypothesis.control import assume
  22. from hypothesis.internal.compat import int_from_bytes
  23. from hypothesis.internal.floats import sign
  24. from hypothesis.searchstrategy.strategies import SearchStrategy, \
  25. MappedSearchStrategy
  26. class IntStrategy(SearchStrategy):
  27. """A generic strategy for integer types that provides the basic methods
  28. other than produce.
  29. Subclasses should provide the produce method.
  30. """
  31. class IntegersFromStrategy(SearchStrategy):
  32. def __init__(self, lower_bound, average_size=100000.0):
  33. super(IntegersFromStrategy, self).__init__()
  34. self.lower_bound = lower_bound
  35. self.average_size = average_size
  36. def __repr__(self):
  37. return 'IntegersFromStrategy(%d)' % (self.lower_bound,)
  38. def do_draw(self, data):
  39. return int(
  40. self.lower_bound + d.geometric(data, 1.0 / self.average_size))
  41. class WideRangeIntStrategy(IntStrategy):
  42. def __repr__(self):
  43. return 'WideRangeIntStrategy()'
  44. def do_draw(self, data):
  45. size = 16
  46. sign_mask = 2 ** (size * 8 - 1)
  47. byt = data.draw_bytes(size)
  48. r = int_from_bytes(byt)
  49. negative = r & sign_mask
  50. r &= (~sign_mask)
  51. if negative:
  52. r = -r
  53. return int(r)
  54. class BoundedIntStrategy(SearchStrategy):
  55. """A strategy for providing integers in some interval with inclusive
  56. endpoints."""
  57. def __init__(self, start, end):
  58. SearchStrategy.__init__(self)
  59. self.start = start
  60. self.end = end
  61. def __repr__(self):
  62. return 'BoundedIntStrategy(%d, %d)' % (self.start, self.end)
  63. def do_draw(self, data):
  64. return d.integer_range(data, self.start, self.end)
  65. NASTY_FLOATS = sorted([
  66. 0.0, 0.5, 1.1, 1.5, 1.9, 1.0 / 3, 10e6, 10e-6, 1.175494351e-38,
  67. 2.2250738585072014e-308,
  68. 1.7976931348623157e+308, 3.402823466e+38, 9007199254740992, 1 - 10e-6,
  69. 2 + 10e-6, 1.192092896e-07, 2.2204460492503131e-016,
  70. ] + [float('inf'), float('nan')] * 5, key=flt.float_to_lex)
  71. NASTY_FLOATS = list(map(float, NASTY_FLOATS))
  72. NASTY_FLOATS.extend([-x for x in NASTY_FLOATS])
  73. class FloatStrategy(SearchStrategy):
  74. """Generic superclass for strategies which produce floats."""
  75. def __init__(self, allow_infinity, allow_nan):
  76. SearchStrategy.__init__(self)
  77. assert isinstance(allow_infinity, bool)
  78. assert isinstance(allow_nan, bool)
  79. self.allow_infinity = allow_infinity
  80. self.allow_nan = allow_nan
  81. self.nasty_floats = [f for f in NASTY_FLOATS if self.permitted(f)]
  82. weights = [
  83. 0.6 * len(self.nasty_floats)
  84. ] + [0.4] * len(self.nasty_floats)
  85. self.sampler = d.Sampler(weights)
  86. def __repr__(self):
  87. return '%s()' % (self.__class__.__name__,)
  88. def permitted(self, f):
  89. assert isinstance(f, float)
  90. if not self.allow_infinity and math.isinf(f):
  91. return False
  92. if not self.allow_nan and math.isnan(f):
  93. return False
  94. return True
  95. def do_draw(self, data):
  96. while True:
  97. data.start_example()
  98. i = self.sampler.sample(data)
  99. if i == 0:
  100. result = flt.draw_float(data)
  101. else:
  102. result = self.nasty_floats[i - 1]
  103. flt.write_float(data, result)
  104. data.stop_example()
  105. if self.permitted(result):
  106. return result
  107. def float_order_key(k):
  108. return (sign(k), k)
  109. class FixedBoundedFloatStrategy(SearchStrategy):
  110. """A strategy for floats distributed between two endpoints.
  111. The conditional distribution tries to produce values clustered
  112. closer to one of the ends.
  113. """
  114. def __init__(self, lower_bound, upper_bound):
  115. SearchStrategy.__init__(self)
  116. self.lower_bound = float(lower_bound)
  117. self.upper_bound = float(upper_bound)
  118. assert not math.isinf(self.upper_bound - self.lower_bound)
  119. lb = float_order_key(self.lower_bound)
  120. ub = float_order_key(self.upper_bound)
  121. self.critical = [
  122. z for z in (-0.0, 0.0)
  123. if lb <= float_order_key(z) <= ub
  124. ]
  125. self.critical.append(self.lower_bound)
  126. self.critical.append(self.upper_bound)
  127. def __repr__(self):
  128. return 'FixedBoundedFloatStrategy(%s, %s)' % (
  129. self.lower_bound, self.upper_bound,
  130. )
  131. def do_draw(self, data):
  132. f = self.lower_bound + (
  133. self.upper_bound - self.lower_bound) * d.fractional_float(data)
  134. assume(self.lower_bound <= f <= self.upper_bound)
  135. assume(sign(self.lower_bound) <= sign(f) <= sign(self.upper_bound))
  136. # Special handling for bounds of -0.0
  137. for g in [self.lower_bound, self.upper_bound]:
  138. if f == g:
  139. f = math.copysign(f, g)
  140. return f
  141. class ComplexStrategy(MappedSearchStrategy):
  142. """A strategy over complex numbers, with real and imaginary values
  143. distributed according to some provided strategy for floating point
  144. numbers."""
  145. def __repr__(self):
  146. return 'ComplexStrategy()'
  147. def pack(self, value):
  148. return complex(*value)