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

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

249 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 Any, Literal, cast 

15 

16from hypothesis.control import reject 

17from hypothesis.errors import CannotInvert, InvalidArgument 

18from hypothesis.internal.conjecture.choice import ChoiceT 

19from hypothesis.internal.conjecture.data import ConjectureData 

20from hypothesis.internal.filtering import ( 

21 get_float_predicate_bounds, 

22 get_integer_predicate_bounds, 

23) 

24from hypothesis.internal.floats import ( 

25 SMALLEST_SUBNORMAL, 

26 float_of, 

27 float_to_int, 

28 int_to_float, 

29 is_negative, 

30 next_down, 

31 next_down_normal, 

32 next_up, 

33 next_up_normal, 

34 sign_aware_lte, 

35 width_smallest_normals, 

36) 

37from hypothesis.internal.validation import ( 

38 check_type, 

39 check_valid_bound, 

40 check_valid_interval, 

41) 

42from hypothesis.strategies._internal.misc import nothing 

43from hypothesis.strategies._internal.strategies import ( 

44 SampledFromStrategy, 

45 SearchStrategy, 

46) 

47from hypothesis.strategies._internal.utils import cacheable, defines_strategy 

48 

49# See https://github.com/python/mypy/issues/3186 - numbers.Real is wrong! 

50Real = int | float | Fraction | Decimal 

51 

52 

53class IntegersStrategy(SearchStrategy[int]): 

54 def __init__(self, start: int | None, end: int | None) -> None: 

55 super().__init__() 

56 assert isinstance(start, int) or start is None 

57 assert isinstance(end, int) or end is None 

58 assert start is None or end is None or start <= end 

59 self.start = start 

60 self.end = end 

61 

62 def __repr__(self) -> str: 

63 if self.start is None and self.end is None: 

64 return "integers()" 

65 if self.end is None: 

66 return f"integers(min_value={self.start})" 

67 if self.start is None: 

68 return f"integers(max_value={self.end})" 

69 return f"integers({self.start}, {self.end})" 

70 

71 def do_draw(self, data: ConjectureData) -> int: 

72 # For bounded integers, make the bounds and near-bounds more likely. 

73 weights = None 

74 if ( 

75 self.end is not None 

76 and self.start is not None 

77 and self.end - self.start > 127 

78 ): 

79 weights = { 

80 self.start: (2 / 128), 

81 self.start + 1: (1 / 128), 

82 self.end - 1: (1 / 128), 

83 self.end: (2 / 128), 

84 } 

85 

86 return data.draw_integer( 

87 min_value=self.start, max_value=self.end, weights=weights 

88 ) 

89 

90 def _invert(self, value: Any) -> tuple[ChoiceT, ...]: 

91 if not isinstance(value, int) or isinstance(value, bool): 

92 raise CannotInvert(f"{value!r} is not an integer") 

93 if self.start is not None and value < self.start: 

94 raise CannotInvert(f"{value!r} is below min_value={self.start!r}") 

95 if self.end is not None and value > self.end: 

96 raise CannotInvert(f"{value!r} is above max_value={self.end!r}") 

97 return (value,) 

98 

99 def filter(self, condition): 

100 if condition is math.isfinite: 

101 return self 

102 if condition in [math.isinf, math.isnan]: 

103 return nothing() 

104 constraints, pred = get_integer_predicate_bounds(condition) 

105 

106 start, end = self.start, self.end 

107 if "min_value" in constraints: 

108 start = max(constraints["min_value"], -math.inf if start is None else start) 

109 if "max_value" in constraints: 

110 end = min(constraints["max_value"], math.inf if end is None else end) 

111 

112 if start != self.start or end != self.end: 

113 if start is not None and end is not None and start > end: 

114 return nothing() 

115 self = type(self)(start, end) 

116 if pred is None: 

117 return self 

118 return super().filter(pred) 

119 

120 

121@cacheable 

122@defines_strategy(force_reusable_values=True) 

123def integers( 

124 min_value: int | None = None, 

125 max_value: int | None = None, 

126) -> SearchStrategy[int]: 

127 """Returns a strategy which generates integers. 

128 

129 If min_value is not None then all values will be >= min_value. If 

130 max_value is not None then all values will be <= max_value 

131 

132 Examples from this strategy will shrink towards zero, and negative values 

133 will also shrink towards positive (i.e. -n may be replaced by +n). 

134 """ 

135 check_valid_bound(min_value, "min_value") 

136 check_valid_bound(max_value, "max_value") 

137 check_valid_interval(min_value, max_value, "min_value", "max_value") 

138 

139 if min_value is not None: 

140 if min_value != int(min_value): 

141 raise InvalidArgument( 

142 f"min_value={min_value!r} of type {type(min_value)!r} " 

143 "cannot be exactly represented as an integer." 

144 ) 

145 min_value = int(min_value) 

146 if max_value is not None: 

147 if max_value != int(max_value): 

148 raise InvalidArgument( 

149 f"max_value={max_value!r} of type {type(max_value)!r} " 

150 "cannot be exactly represented as an integer." 

151 ) 

152 max_value = int(max_value) 

153 

154 return IntegersStrategy(min_value, max_value) 

155 

156 

157class FloatStrategy(SearchStrategy[float]): 

158 """A strategy for floating point numbers.""" 

159 

160 def __init__( 

161 self, 

162 *, 

163 min_value: float, 

164 max_value: float, 

165 allow_nan: bool, 

166 # The smallest nonzero number we can represent is usually a subnormal, but may 

167 # be the smallest normal if we're running in unsafe denormals-are-zero mode. 

168 # While that's usually an explicit error, we do need to handle the case where 

169 # the user passes allow_subnormal=False. 

170 smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL, 

171 ): 

172 super().__init__() 

173 assert isinstance(allow_nan, bool) 

174 assert smallest_nonzero_magnitude >= 0.0, "programmer error if this is negative" 

175 if smallest_nonzero_magnitude == 0.0: # pragma: no cover 

176 raise FloatingPointError( 

177 "Got allow_subnormal=True, but we can't represent subnormal floats " 

178 "right now, in violation of the IEEE-754 floating-point " 

179 "specification. This is usually because something was compiled with " 

180 "-ffast-math or a similar option, which sets global processor state. " 

181 "See https://simonbyrne.github.io/notes/fastmath/ for a more detailed " 

182 "writeup - and good luck!" 

183 ) 

184 self.min_value = min_value 

185 self.max_value = max_value 

186 self.allow_nan = allow_nan 

187 self.smallest_nonzero_magnitude = smallest_nonzero_magnitude 

188 

189 def __repr__(self) -> str: 

190 return ( 

191 f"{self.__class__.__name__}({self.min_value=}, {self.max_value=}, " 

192 f"{self.allow_nan=}, {self.smallest_nonzero_magnitude=})" 

193 ).replace("self.", "") 

194 

195 def do_draw(self, data: ConjectureData) -> float: 

196 return data.draw_float( 

197 min_value=self.min_value, 

198 max_value=self.max_value, 

199 allow_nan=self.allow_nan, 

200 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

201 ) 

202 

203 def _invert(self, value: Any) -> tuple[ChoiceT, ...]: 

204 if type(value) is not float: 

205 raise CannotInvert(f"{value!r} is not a float") 

206 if math.isnan(value): 

207 if not self.allow_nan: 

208 raise CannotInvert(f"NaN not permitted by {self!r}") 

209 return (value,) 

210 if 0 < abs(value) < self.smallest_nonzero_magnitude: 

211 raise CannotInvert( 

212 f"{value!r} below smallest_nonzero_magnitude=" 

213 f"{self.smallest_nonzero_magnitude!r}" 

214 ) 

215 if not sign_aware_lte(self.min_value, value) or not sign_aware_lte( 

216 value, self.max_value 

217 ): 

218 raise CannotInvert( 

219 f"{value!r} outside [{self.min_value!r}, {self.max_value!r}]" 

220 ) 

221 return (value,) 

222 

223 def filter(self, condition): 

224 # Handle a few specific weird cases. 

225 if condition is math.isfinite: 

226 return FloatStrategy( 

227 min_value=max(self.min_value, next_up(float("-inf"))), 

228 max_value=min(self.max_value, next_down(float("inf"))), 

229 allow_nan=False, 

230 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

231 ) 

232 if condition is math.isinf: 

233 if permitted_infs := [ 

234 x 

235 for x in (-math.inf, math.inf) 

236 if self.min_value <= x <= self.max_value 

237 ]: 

238 return SampledFromStrategy(permitted_infs) 

239 return nothing() 

240 if condition is math.isnan: 

241 if not self.allow_nan: 

242 return nothing() 

243 return NanStrategy() 

244 

245 constraints, pred = get_float_predicate_bounds(condition) 

246 if not constraints: 

247 return super().filter(pred) 

248 min_bound = max(constraints.get("min_value", -math.inf), self.min_value) 

249 max_bound = min(constraints.get("max_value", math.inf), self.max_value) 

250 

251 # Adjustments for allow_subnormal=False, if any need to be made 

252 if -self.smallest_nonzero_magnitude < min_bound < 0: 

253 min_bound = -0.0 

254 elif 0 < min_bound < self.smallest_nonzero_magnitude: 

255 min_bound = self.smallest_nonzero_magnitude 

256 if -self.smallest_nonzero_magnitude < max_bound < 0: 

257 max_bound = -self.smallest_nonzero_magnitude 

258 elif 0 < max_bound < self.smallest_nonzero_magnitude: 

259 max_bound = 0.0 

260 

261 if min_bound > max_bound: 

262 return nothing() 

263 if ( 

264 min_bound > self.min_value 

265 or self.max_value > max_bound 

266 or (self.allow_nan and (-math.inf < min_bound or max_bound < math.inf)) 

267 ): 

268 self = type(self)( 

269 min_value=min_bound, 

270 max_value=max_bound, 

271 allow_nan=False, 

272 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

273 ) 

274 if pred is None: 

275 return self 

276 return super().filter(pred) 

277 

278 

279@cacheable 

280@defines_strategy(force_reusable_values=True) 

281def floats( 

282 min_value: Real | None = None, 

283 max_value: Real | None = None, 

284 *, 

285 allow_nan: bool | None = None, 

286 allow_infinity: bool | None = None, 

287 allow_subnormal: bool | None = None, 

288 width: Literal[16, 32, 64] = 64, 

289 exclude_min: bool = False, 

290 exclude_max: bool = False, 

291) -> SearchStrategy[float]: 

292 """Returns a strategy which generates floats. 

293 

294 - If min_value is not None, all values will be ``>= min_value`` 

295 (or ``> min_value`` if ``exclude_min``). 

296 - If max_value is not None, all values will be ``<= max_value`` 

297 (or ``< max_value`` if ``exclude_max``). 

298 - If min_value or max_value is not None, it is an error to enable 

299 allow_nan. 

300 - If both min_value and max_value are not None, it is an error to enable 

301 allow_infinity. 

302 - If inferred values range does not include subnormal values, it is an error 

303 to enable allow_subnormal. 

304 

305 Where not explicitly ruled out by the bounds, 

306 :wikipedia:`subnormals <Subnormal_number>`, infinities, and NaNs are possible 

307 values generated by this strategy. 

308 

309 The width argument specifies the maximum number of bits of precision 

310 required to represent the generated float. Valid values are 16, 32, or 64. 

311 Passing ``width=32`` will still use the builtin 64-bit :class:`~python:float` class, 

312 but always for values which can be exactly represented as a 32-bit float. 

313 

314 The exclude_min and exclude_max argument can be used to generate numbers 

315 from open or half-open intervals, by excluding the respective endpoints. 

316 Excluding either signed zero will also exclude the other. 

317 Attempting to exclude an endpoint which is None will raise an error; 

318 use ``allow_infinity=False`` to generate finite floats. You can however 

319 use e.g. ``min_value=-math.inf, exclude_min=True`` to exclude only 

320 one infinite endpoint. 

321 

322 Examples from this strategy have a complicated and hard to explain 

323 shrinking behaviour, but it tries to improve "human readability". Finite 

324 numbers will be preferred to infinity and infinity will be preferred to 

325 NaN. 

326 """ 

327 check_type(bool, exclude_min, "exclude_min") 

328 check_type(bool, exclude_max, "exclude_max") 

329 

330 if allow_nan is None: 

331 allow_nan = bool(min_value is None and max_value is None) 

332 elif allow_nan and (min_value is not None or max_value is not None): 

333 raise InvalidArgument(f"Cannot have {allow_nan=}, with min_value or max_value") 

334 

335 if width not in (16, 32, 64): 

336 raise InvalidArgument( 

337 f"Got {width=}, but the only valid values " 

338 "are the integers 16, 32, and 64." 

339 ) 

340 # Literal[16] accepts both 16 and 16.0. Normalize to the int 16 here, mainly 

341 # for mypyc. We want to support width=16.0 to make e.g. width=mywidth / 2 for 

342 # mywidth=32 easy. 

343 width = cast(Literal[16, 32, 64], int(width)) 

344 

345 check_valid_bound(min_value, "min_value") 

346 check_valid_bound(max_value, "max_value") 

347 

348 if math.copysign(1.0, -0.0) == 1.0: # pragma: no cover 

349 raise FloatingPointError( 

350 "Your Python install can't represent -0.0, which is required by the " 

351 "IEEE-754 floating-point specification. This is probably because it was " 

352 "compiled with an unsafe option like -ffast-math; for a more detailed " 

353 "explanation see https://simonbyrne.github.io/notes/fastmath/" 

354 ) 

355 if allow_subnormal and next_up(0.0, width=width) == 0: # pragma: no cover 

356 # Not worth having separate CI envs and dependencies just to cover this branch; 

357 # discussion in https://github.com/HypothesisWorks/hypothesis/issues/3092 

358 # 

359 # Erroring out here ensures that the database contents are interpreted 

360 # consistently - which matters for such a foundational strategy, even if it's 

361 # not always true for all user-composed strategies further up the stack. 

362 from _hypothesis_ftz_detector import identify_ftz_culprits 

363 

364 try: 

365 ftz_pkg = identify_ftz_culprits() 

366 except Exception: 

367 ftz_pkg = None 

368 if ftz_pkg: 

369 ftz_msg = ( 

370 f"This seems to be because the `{ftz_pkg}` package was compiled with " 

371 f"-ffast-math or a similar option, which sets global processor state " 

372 f"- see https://simonbyrne.github.io/notes/fastmath/ for details. " 

373 f"If you don't know why {ftz_pkg} is installed, `pipdeptree -rp " 

374 f"{ftz_pkg}` will show which packages depend on it." 

375 ) 

376 else: 

377 ftz_msg = ( 

378 "This is usually because something was compiled with -ffast-math " 

379 "or a similar option, which sets global processor state. See " 

380 "https://simonbyrne.github.io/notes/fastmath/ for a more detailed " 

381 "writeup - and good luck!" 

382 ) 

383 raise FloatingPointError( 

384 f"Got {allow_subnormal=}, but we can't represent " 

385 f"subnormal floats right now, in violation of the IEEE-754 floating-point " 

386 f"specification. {ftz_msg}" 

387 ) 

388 

389 min_arg, max_arg = min_value, max_value 

390 if min_value is not None: 

391 min_value = float_of(min_value, width) 

392 assert isinstance(min_value, float) 

393 if max_value is not None: 

394 max_value = float_of(max_value, width) 

395 assert isinstance(max_value, float) 

396 

397 if min_value != min_arg: 

398 raise InvalidArgument( 

399 f"min_value={min_arg!r} cannot be exactly represented as a float " 

400 f"of width {width} - use {min_value=} instead." 

401 ) 

402 if max_value != max_arg: 

403 raise InvalidArgument( 

404 f"max_value={max_arg!r} cannot be exactly represented as a float " 

405 f"of width {width} - use {max_value=} instead." 

406 ) 

407 

408 if exclude_min and (min_value is None or min_value == math.inf): 

409 raise InvalidArgument(f"Cannot exclude {min_value=}") 

410 if exclude_max and (max_value is None or max_value == -math.inf): 

411 raise InvalidArgument(f"Cannot exclude {max_value=}") 

412 

413 assumed_allow_subnormal = allow_subnormal is None or allow_subnormal 

414 if min_value is not None and ( 

415 exclude_min or (min_arg is not None and min_value < min_arg) 

416 ): 

417 min_value = next_up_normal( 

418 min_value, width, allow_subnormal=assumed_allow_subnormal 

419 ) 

420 if min_value == min_arg: 

421 assert min_value == min_arg == 0 

422 assert is_negative(min_arg) 

423 assert not is_negative(min_value) 

424 min_value = next_up_normal( 

425 min_value, width, allow_subnormal=assumed_allow_subnormal 

426 ) 

427 assert min_value > min_arg 

428 if max_value is not None and ( 

429 exclude_max or (max_arg is not None and max_value > max_arg) 

430 ): 

431 max_value = next_down_normal( 

432 max_value, width, allow_subnormal=assumed_allow_subnormal 

433 ) 

434 if max_value == max_arg: 

435 assert max_value == max_arg == 0 

436 assert is_negative(max_value) 

437 assert not is_negative(max_arg) 

438 max_value = next_down_normal( 

439 max_value, width, allow_subnormal=assumed_allow_subnormal 

440 ) 

441 assert max_value < max_arg 

442 

443 if min_value == -math.inf: 

444 min_value = None 

445 if max_value == math.inf: 

446 max_value = None 

447 

448 bad_zero_bounds = ( 

449 min_value == max_value == 0 

450 and is_negative(max_value) 

451 and not is_negative(min_value) 

452 ) 

453 if ( 

454 min_value is not None 

455 and max_value is not None 

456 and (min_value > max_value or bad_zero_bounds) 

457 ): 

458 # This is a custom alternative to check_valid_interval, because we want 

459 # to include the bit-width and exclusion information in the message. 

460 msg = ( 

461 f"There are no {width}-bit floating-point values between " 

462 f"min_value={min_arg!r} and max_value={max_arg!r}" 

463 ) 

464 if exclude_min or exclude_max: 

465 msg += f", {exclude_min=} and {exclude_max=}" 

466 raise InvalidArgument(msg) 

467 

468 if allow_infinity is None: 

469 allow_infinity = bool(min_value is None or max_value is None) 

470 elif allow_infinity: 

471 if min_value is not None and max_value is not None: 

472 raise InvalidArgument( 

473 f"Cannot have {allow_infinity=}, with both min_value and max_value" 

474 ) 

475 elif min_value == math.inf: 

476 if min_arg == math.inf: 

477 raise InvalidArgument("allow_infinity=False excludes min_value=inf") 

478 raise InvalidArgument( 

479 f"exclude_min=True turns min_value={min_arg!r} into inf, " 

480 "but allow_infinity=False" 

481 ) 

482 elif max_value == -math.inf: 

483 if max_arg == -math.inf: 

484 raise InvalidArgument("allow_infinity=False excludes max_value=-inf") 

485 raise InvalidArgument( 

486 f"exclude_max=True turns max_value={max_arg!r} into -inf, " 

487 "but allow_infinity=False" 

488 ) 

489 

490 smallest_normal = width_smallest_normals(width) 

491 if allow_subnormal is None: 

492 if min_value is not None and max_value is not None: 

493 if min_value == max_value: 

494 allow_subnormal = -smallest_normal < min_value < smallest_normal 

495 else: 

496 allow_subnormal = ( 

497 min_value < smallest_normal and max_value > -smallest_normal 

498 ) 

499 elif min_value is not None: 

500 allow_subnormal = min_value < smallest_normal 

501 elif max_value is not None: 

502 allow_subnormal = max_value > -smallest_normal 

503 else: 

504 allow_subnormal = True 

505 if allow_subnormal: 

506 if min_value is not None and min_value >= smallest_normal: 

507 raise InvalidArgument( 

508 f"allow_subnormal=True, but minimum value {min_value} " 

509 f"excludes values below float{width}'s " 

510 f"smallest positive normal {smallest_normal}" 

511 ) 

512 if max_value is not None and max_value <= -smallest_normal: 

513 raise InvalidArgument( 

514 f"allow_subnormal=True, but maximum value {max_value} " 

515 f"excludes values above float{width}'s " 

516 f"smallest negative normal {-smallest_normal}" 

517 ) 

518 

519 if min_value is None: 

520 min_value = float("-inf") 

521 if max_value is None: 

522 max_value = float("inf") 

523 if not allow_infinity: 

524 min_value = max(min_value, next_up(float("-inf"))) 

525 max_value = min(max_value, next_down(float("inf"))) 

526 assert isinstance(min_value, float) 

527 assert isinstance(max_value, float) 

528 smallest_nonzero_magnitude = ( 

529 SMALLEST_SUBNORMAL if allow_subnormal else smallest_normal 

530 ) 

531 result: SearchStrategy = FloatStrategy( 

532 min_value=min_value, 

533 max_value=max_value, 

534 allow_nan=allow_nan, 

535 smallest_nonzero_magnitude=smallest_nonzero_magnitude, 

536 ) 

537 

538 if width < 64: 

539 

540 def downcast(x: float) -> float: 

541 try: 

542 return float_of(x, width) 

543 except OverflowError: 

544 reject() 

545 

546 result = result.map(downcast) 

547 return result 

548 

549 

550class NanStrategy(SearchStrategy[float]): 

551 """Strategy for sampling the space of nan float values.""" 

552 

553 def do_draw(self, data: ConjectureData) -> float: 

554 # Nans must have all exponent bits and the first mantissa bit set, so 

555 # we generate by taking 64 random bits and setting the required ones. 

556 sign_bit = int(data.draw_boolean()) << 63 

557 nan_bits = float_to_int(math.nan) 

558 mantissa_bits = data.draw_integer(0, 2**52 - 1) 

559 return int_to_float(sign_bit | nan_bits | mantissa_bits) 

560 

561 def _invert(self, value: Any) -> tuple[ChoiceT, ...]: 

562 if not isinstance(value, float) or not math.isnan(value): 

563 raise CannotInvert(f"{value!r} is not NaN") 

564 bits = float_to_int(value) 

565 return (bool(bits >> 63), bits & ((1 << 52) - 1))