Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/hypothesis/strategies/_internal/numbers.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

218 statements  

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)