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

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

219 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.conjecture.data import ConjectureData 

19from hypothesis.internal.filtering import ( 

20 get_float_predicate_bounds, 

21 get_integer_predicate_bounds, 

22) 

23from hypothesis.internal.floats import ( 

24 SMALLEST_SUBNORMAL, 

25 float_of, 

26 float_to_int, 

27 int_to_float, 

28 is_negative, 

29 next_down, 

30 next_down_normal, 

31 next_up, 

32 next_up_normal, 

33 width_smallest_normals, 

34) 

35from hypothesis.internal.validation import ( 

36 check_type, 

37 check_valid_bound, 

38 check_valid_interval, 

39) 

40from hypothesis.strategies._internal.misc import nothing 

41from hypothesis.strategies._internal.strategies import ( 

42 SampledFromStrategy, 

43 SearchStrategy, 

44) 

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

46 

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

48Real = Union[int, float, Fraction, Decimal] 

49 

50 

51class IntegersStrategy(SearchStrategy[int]): 

52 def __init__(self, start: Optional[int], end: Optional[int]) -> None: 

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

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

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

56 self.start = start 

57 self.end = end 

58 

59 def __repr__(self) -> str: 

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

61 return "integers()" 

62 if self.end is None: 

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

64 if self.start is None: 

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

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

67 

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

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

70 weights = None 

71 if ( 

72 self.end is not None 

73 and self.start is not None 

74 and self.end - self.start > 127 

75 ): 

76 weights = { 

77 self.start: (2 / 128), 

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

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

80 self.end: (2 / 128), 

81 } 

82 

83 return data.draw_integer( 

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

85 ) 

86 

87 def filter(self, condition): 

88 if condition is math.isfinite: 

89 return self 

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

91 return nothing() 

92 constraints, pred = get_integer_predicate_bounds(condition) 

93 

94 start, end = self.start, self.end 

95 if "min_value" in constraints: 

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

97 if "max_value" in constraints: 

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

99 

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

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

102 return nothing() 

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

104 if pred is None: 

105 return self 

106 return super().filter(pred) 

107 

108 

109@cacheable 

110@defines_strategy(force_reusable_values=True) 

111def integers( 

112 min_value: Optional[int] = None, 

113 max_value: Optional[int] = None, 

114) -> SearchStrategy[int]: 

115 """Returns a strategy which generates integers. 

116 

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

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

119 

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

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

122 """ 

123 check_valid_bound(min_value, "min_value") 

124 check_valid_bound(max_value, "max_value") 

125 check_valid_interval(min_value, max_value, "min_value", "max_value") 

126 

127 if min_value is not None: 

128 if min_value != int(min_value): 

129 raise InvalidArgument( 

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

131 "cannot be exactly represented as an integer." 

132 ) 

133 min_value = int(min_value) 

134 if max_value is not None: 

135 if max_value != int(max_value): 

136 raise InvalidArgument( 

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

138 "cannot be exactly represented as an integer." 

139 ) 

140 max_value = int(max_value) 

141 

142 return IntegersStrategy(min_value, max_value) 

143 

144 

145class FloatStrategy(SearchStrategy[float]): 

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

147 

148 def __init__( 

149 self, 

150 *, 

151 min_value: float, 

152 max_value: float, 

153 allow_nan: bool, 

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

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

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

157 # the user passes allow_subnormal=False. 

158 smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL, 

159 ): 

160 super().__init__() 

161 assert isinstance(allow_nan, bool) 

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

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

164 raise FloatingPointError( 

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

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

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

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

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

170 "writeup - and good luck!" 

171 ) 

172 self.min_value = min_value 

173 self.max_value = max_value 

174 self.allow_nan = allow_nan 

175 self.smallest_nonzero_magnitude = smallest_nonzero_magnitude 

176 

177 def __repr__(self) -> str: 

178 return ( 

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

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

181 ).replace("self.", "") 

182 

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

184 return data.draw_float( 

185 min_value=self.min_value, 

186 max_value=self.max_value, 

187 allow_nan=self.allow_nan, 

188 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

189 ) 

190 

191 def filter(self, condition): 

192 # Handle a few specific weird cases. 

193 if condition is math.isfinite: 

194 return FloatStrategy( 

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

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

197 allow_nan=False, 

198 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

199 ) 

200 if condition is math.isinf: 

201 if permitted_infs := [ 

202 x 

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

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

205 ]: 

206 return SampledFromStrategy(permitted_infs) 

207 return nothing() 

208 if condition is math.isnan: 

209 if not self.allow_nan: 

210 return nothing() 

211 return NanStrategy() 

212 

213 constraints, pred = get_float_predicate_bounds(condition) 

214 if not constraints: 

215 return super().filter(pred) 

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

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

218 

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

220 if -self.smallest_nonzero_magnitude < min_bound < 0: 

221 min_bound = -0.0 

222 elif 0 < min_bound < self.smallest_nonzero_magnitude: 

223 min_bound = self.smallest_nonzero_magnitude 

224 if -self.smallest_nonzero_magnitude < max_bound < 0: 

225 max_bound = -self.smallest_nonzero_magnitude 

226 elif 0 < max_bound < self.smallest_nonzero_magnitude: 

227 max_bound = 0.0 

228 

229 if min_bound > max_bound: 

230 return nothing() 

231 if ( 

232 min_bound > self.min_value 

233 or self.max_value > max_bound 

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

235 ): 

236 self = type(self)( 

237 min_value=min_bound, 

238 max_value=max_bound, 

239 allow_nan=False, 

240 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

241 ) 

242 if pred is None: 

243 return self 

244 return super().filter(pred) 

245 

246 

247@cacheable 

248@defines_strategy(force_reusable_values=True) 

249def floats( 

250 min_value: Optional[Real] = None, 

251 max_value: Optional[Real] = None, 

252 *, 

253 allow_nan: Optional[bool] = None, 

254 allow_infinity: Optional[bool] = None, 

255 allow_subnormal: Optional[bool] = None, 

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

257 exclude_min: bool = False, 

258 exclude_max: bool = False, 

259) -> SearchStrategy[float]: 

260 """Returns a strategy which generates floats. 

261 

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

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

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

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

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

267 allow_nan. 

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

269 allow_infinity. 

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

271 to enable allow_subnormal. 

272 

273 Where not explicitly ruled out by the bounds, 

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

275 values generated by this strategy. 

276 

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

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

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

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

281 

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

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

284 Excluding either signed zero will also exclude the other. 

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

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

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

288 one infinite endpoint. 

289 

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

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

292 numbers will be preferred to infinity and infinity will be preferred to 

293 NaN. 

294 """ 

295 check_type(bool, exclude_min, "exclude_min") 

296 check_type(bool, exclude_max, "exclude_max") 

297 

298 if allow_nan is None: 

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

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

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

302 

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

304 raise InvalidArgument( 

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

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

307 ) 

308 

309 check_valid_bound(min_value, "min_value") 

310 check_valid_bound(max_value, "max_value") 

311 

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

313 raise FloatingPointError( 

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

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

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

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

318 ) 

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

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

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

322 # 

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

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

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

326 from _hypothesis_ftz_detector import identify_ftz_culprits 

327 

328 try: 

329 ftz_pkg = identify_ftz_culprits() 

330 except Exception: 

331 ftz_pkg = None 

332 if ftz_pkg: 

333 ftz_msg = ( 

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

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

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

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

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

339 ) 

340 else: 

341 ftz_msg = ( 

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

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

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

345 "writeup - and good luck!" 

346 ) 

347 raise FloatingPointError( 

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

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

350 f"specification. {ftz_msg}" 

351 ) 

352 

353 min_arg, max_arg = min_value, max_value 

354 if min_value is not None: 

355 min_value = float_of(min_value, width) 

356 assert isinstance(min_value, float) 

357 if max_value is not None: 

358 max_value = float_of(max_value, width) 

359 assert isinstance(max_value, float) 

360 

361 if min_value != min_arg: 

362 raise InvalidArgument( 

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

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

365 ) 

366 if max_value != max_arg: 

367 raise InvalidArgument( 

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

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

370 ) 

371 

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

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

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

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

376 

377 assumed_allow_subnormal = allow_subnormal is None or allow_subnormal 

378 if min_value is not None and ( 

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

380 ): 

381 min_value = next_up_normal( 

382 min_value, width, allow_subnormal=assumed_allow_subnormal 

383 ) 

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( 

389 min_value, width, allow_subnormal=assumed_allow_subnormal 

390 ) 

391 assert min_value > min_arg # type: ignore 

392 if max_value is not None and ( 

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

394 ): 

395 max_value = next_down_normal( 

396 max_value, width, allow_subnormal=assumed_allow_subnormal 

397 ) 

398 if max_value == max_arg: 

399 assert max_value == max_arg == 0 

400 assert is_negative(max_value) 

401 assert not is_negative(max_arg) 

402 max_value = next_down_normal( 

403 max_value, width, allow_subnormal=assumed_allow_subnormal 

404 ) 

405 assert max_value < max_arg # type: ignore 

406 

407 if min_value == -math.inf: 

408 min_value = None 

409 if max_value == math.inf: 

410 max_value = None 

411 

412 bad_zero_bounds = ( 

413 min_value == max_value == 0 

414 and is_negative(max_value) 

415 and not is_negative(min_value) 

416 ) 

417 if ( 

418 min_value is not None 

419 and max_value is not None 

420 and (min_value > max_value or bad_zero_bounds) 

421 ): 

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

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

424 msg = ( 

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

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

427 ) 

428 if exclude_min or exclude_max: 

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

430 raise InvalidArgument(msg) 

431 

432 if allow_infinity is None: 

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

434 elif allow_infinity: 

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

436 raise InvalidArgument( 

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

438 ) 

439 elif min_value == math.inf: 

440 if min_arg == math.inf: 

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

442 raise InvalidArgument( 

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

444 "but allow_infinity=False" 

445 ) 

446 elif max_value == -math.inf: 

447 if max_arg == -math.inf: 

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

449 raise InvalidArgument( 

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

451 "but allow_infinity=False" 

452 ) 

453 

454 smallest_normal = width_smallest_normals[width] 

455 if allow_subnormal is None: 

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

457 if min_value == max_value: 

458 allow_subnormal = -smallest_normal < min_value < smallest_normal 

459 else: 

460 allow_subnormal = ( 

461 min_value < smallest_normal and max_value > -smallest_normal 

462 ) 

463 elif min_value is not None: 

464 allow_subnormal = min_value < smallest_normal 

465 elif max_value is not None: 

466 allow_subnormal = max_value > -smallest_normal 

467 else: 

468 allow_subnormal = True 

469 if allow_subnormal: 

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

471 raise InvalidArgument( 

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

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

474 f"smallest positive normal {smallest_normal}" 

475 ) 

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

477 raise InvalidArgument( 

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

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

480 f"smallest negative normal {-smallest_normal}" 

481 ) 

482 

483 if min_value is None: 

484 min_value = float("-inf") 

485 if max_value is None: 

486 max_value = float("inf") 

487 if not allow_infinity: 

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

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

490 assert isinstance(min_value, float) 

491 assert isinstance(max_value, float) 

492 smallest_nonzero_magnitude = ( 

493 SMALLEST_SUBNORMAL if allow_subnormal else smallest_normal 

494 ) 

495 result: SearchStrategy = FloatStrategy( 

496 min_value=min_value, 

497 max_value=max_value, 

498 allow_nan=allow_nan, 

499 smallest_nonzero_magnitude=smallest_nonzero_magnitude, 

500 ) 

501 

502 if width < 64: 

503 

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

505 try: 

506 return float_of(x, width) 

507 except OverflowError: # pragma: no cover 

508 reject() 

509 

510 result = result.map(downcast) 

511 return result 

512 

513 

514class NanStrategy(SearchStrategy[float]): 

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

516 

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

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

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

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

521 nan_bits = float_to_int(math.nan) 

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

523 return int_to_float(sign_bit | nan_bits | mantissa_bits)