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

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

220 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 super().__init__() 

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

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

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

57 self.start = start 

58 self.end = end 

59 

60 def __repr__(self) -> str: 

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

62 return "integers()" 

63 if self.end is None: 

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

65 if self.start is None: 

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

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

68 

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

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

71 weights = None 

72 if ( 

73 self.end is not None 

74 and self.start is not None 

75 and self.end - self.start > 127 

76 ): 

77 weights = { 

78 self.start: (2 / 128), 

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

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

81 self.end: (2 / 128), 

82 } 

83 

84 return data.draw_integer( 

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

86 ) 

87 

88 def filter(self, condition): 

89 if condition is math.isfinite: 

90 return self 

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

92 return nothing() 

93 constraints, pred = get_integer_predicate_bounds(condition) 

94 

95 start, end = self.start, self.end 

96 if "min_value" in constraints: 

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

98 if "max_value" in constraints: 

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

100 

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

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

103 return nothing() 

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

105 if pred is None: 

106 return self 

107 return super().filter(pred) 

108 

109 

110@cacheable 

111@defines_strategy(force_reusable_values=True) 

112def integers( 

113 min_value: Optional[int] = None, 

114 max_value: Optional[int] = None, 

115) -> SearchStrategy[int]: 

116 """Returns a strategy which generates integers. 

117 

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

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

120 

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

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

123 """ 

124 check_valid_bound(min_value, "min_value") 

125 check_valid_bound(max_value, "max_value") 

126 check_valid_interval(min_value, max_value, "min_value", "max_value") 

127 

128 if min_value is not None: 

129 if min_value != int(min_value): 

130 raise InvalidArgument( 

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

132 "cannot be exactly represented as an integer." 

133 ) 

134 min_value = int(min_value) 

135 if max_value is not None: 

136 if max_value != int(max_value): 

137 raise InvalidArgument( 

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

139 "cannot be exactly represented as an integer." 

140 ) 

141 max_value = int(max_value) 

142 

143 return IntegersStrategy(min_value, max_value) 

144 

145 

146class FloatStrategy(SearchStrategy[float]): 

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

148 

149 def __init__( 

150 self, 

151 *, 

152 min_value: float, 

153 max_value: float, 

154 allow_nan: bool, 

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

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

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

158 # the user passes allow_subnormal=False. 

159 smallest_nonzero_magnitude: float = SMALLEST_SUBNORMAL, 

160 ): 

161 super().__init__() 

162 assert isinstance(allow_nan, bool) 

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

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

165 raise FloatingPointError( 

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

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

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

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

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

171 "writeup - and good luck!" 

172 ) 

173 self.min_value = min_value 

174 self.max_value = max_value 

175 self.allow_nan = allow_nan 

176 self.smallest_nonzero_magnitude = smallest_nonzero_magnitude 

177 

178 def __repr__(self) -> str: 

179 return ( 

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

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

182 ).replace("self.", "") 

183 

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

185 return data.draw_float( 

186 min_value=self.min_value, 

187 max_value=self.max_value, 

188 allow_nan=self.allow_nan, 

189 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

190 ) 

191 

192 def filter(self, condition): 

193 # Handle a few specific weird cases. 

194 if condition is math.isfinite: 

195 return FloatStrategy( 

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

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

198 allow_nan=False, 

199 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

200 ) 

201 if condition is math.isinf: 

202 if permitted_infs := [ 

203 x 

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

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

206 ]: 

207 return SampledFromStrategy(permitted_infs) 

208 return nothing() 

209 if condition is math.isnan: 

210 if not self.allow_nan: 

211 return nothing() 

212 return NanStrategy() 

213 

214 constraints, pred = get_float_predicate_bounds(condition) 

215 if not constraints: 

216 return super().filter(pred) 

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

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

219 

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

221 if -self.smallest_nonzero_magnitude < min_bound < 0: 

222 min_bound = -0.0 

223 elif 0 < min_bound < self.smallest_nonzero_magnitude: 

224 min_bound = self.smallest_nonzero_magnitude 

225 if -self.smallest_nonzero_magnitude < max_bound < 0: 

226 max_bound = -self.smallest_nonzero_magnitude 

227 elif 0 < max_bound < self.smallest_nonzero_magnitude: 

228 max_bound = 0.0 

229 

230 if min_bound > max_bound: 

231 return nothing() 

232 if ( 

233 min_bound > self.min_value 

234 or self.max_value > max_bound 

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

236 ): 

237 self = type(self)( 

238 min_value=min_bound, 

239 max_value=max_bound, 

240 allow_nan=False, 

241 smallest_nonzero_magnitude=self.smallest_nonzero_magnitude, 

242 ) 

243 if pred is None: 

244 return self 

245 return super().filter(pred) 

246 

247 

248@cacheable 

249@defines_strategy(force_reusable_values=True) 

250def floats( 

251 min_value: Optional[Real] = None, 

252 max_value: Optional[Real] = None, 

253 *, 

254 allow_nan: Optional[bool] = None, 

255 allow_infinity: Optional[bool] = None, 

256 allow_subnormal: Optional[bool] = None, 

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

258 exclude_min: bool = False, 

259 exclude_max: bool = False, 

260) -> SearchStrategy[float]: 

261 """Returns a strategy which generates floats. 

262 

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

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

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

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

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

268 allow_nan. 

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

270 allow_infinity. 

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

272 to enable allow_subnormal. 

273 

274 Where not explicitly ruled out by the bounds, 

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

276 values generated by this strategy. 

277 

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

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

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

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

282 

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

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

285 Excluding either signed zero will also exclude the other. 

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

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

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

289 one infinite endpoint. 

290 

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

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

293 numbers will be preferred to infinity and infinity will be preferred to 

294 NaN. 

295 """ 

296 check_type(bool, exclude_min, "exclude_min") 

297 check_type(bool, exclude_max, "exclude_max") 

298 

299 if allow_nan is None: 

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

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

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

303 

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

305 raise InvalidArgument( 

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

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

308 ) 

309 

310 check_valid_bound(min_value, "min_value") 

311 check_valid_bound(max_value, "max_value") 

312 

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

314 raise FloatingPointError( 

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

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

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

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

319 ) 

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

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

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

323 # 

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

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

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

327 from _hypothesis_ftz_detector import identify_ftz_culprits 

328 

329 try: 

330 ftz_pkg = identify_ftz_culprits() 

331 except Exception: 

332 ftz_pkg = None 

333 if ftz_pkg: 

334 ftz_msg = ( 

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

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

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

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

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

340 ) 

341 else: 

342 ftz_msg = ( 

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

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

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

346 "writeup - and good luck!" 

347 ) 

348 raise FloatingPointError( 

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

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

351 f"specification. {ftz_msg}" 

352 ) 

353 

354 min_arg, max_arg = min_value, max_value 

355 if min_value is not None: 

356 min_value = float_of(min_value, width) 

357 assert isinstance(min_value, float) 

358 if max_value is not None: 

359 max_value = float_of(max_value, width) 

360 assert isinstance(max_value, float) 

361 

362 if min_value != min_arg: 

363 raise InvalidArgument( 

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

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

366 ) 

367 if max_value != max_arg: 

368 raise InvalidArgument( 

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

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

371 ) 

372 

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

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

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

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

377 

378 assumed_allow_subnormal = allow_subnormal is None or allow_subnormal 

379 if min_value is not None and ( 

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

381 ): 

382 min_value = next_up_normal( 

383 min_value, width, allow_subnormal=assumed_allow_subnormal 

384 ) 

385 if min_value == min_arg: 

386 assert min_value == min_arg == 0 

387 assert is_negative(min_arg) 

388 assert not is_negative(min_value) 

389 min_value = next_up_normal( 

390 min_value, width, allow_subnormal=assumed_allow_subnormal 

391 ) 

392 assert min_value > min_arg # type: ignore 

393 if max_value is not None and ( 

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

395 ): 

396 max_value = next_down_normal( 

397 max_value, width, allow_subnormal=assumed_allow_subnormal 

398 ) 

399 if max_value == max_arg: 

400 assert max_value == max_arg == 0 

401 assert is_negative(max_value) 

402 assert not is_negative(max_arg) 

403 max_value = next_down_normal( 

404 max_value, width, allow_subnormal=assumed_allow_subnormal 

405 ) 

406 assert max_value < max_arg # type: ignore 

407 

408 if min_value == -math.inf: 

409 min_value = None 

410 if max_value == math.inf: 

411 max_value = None 

412 

413 bad_zero_bounds = ( 

414 min_value == max_value == 0 

415 and is_negative(max_value) 

416 and not is_negative(min_value) 

417 ) 

418 if ( 

419 min_value is not None 

420 and max_value is not None 

421 and (min_value > max_value or bad_zero_bounds) 

422 ): 

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

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

425 msg = ( 

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

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

428 ) 

429 if exclude_min or exclude_max: 

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

431 raise InvalidArgument(msg) 

432 

433 if allow_infinity is None: 

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

435 elif allow_infinity: 

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

437 raise InvalidArgument( 

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

439 ) 

440 elif min_value == math.inf: 

441 if min_arg == math.inf: 

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

443 raise InvalidArgument( 

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

445 "but allow_infinity=False" 

446 ) 

447 elif max_value == -math.inf: 

448 if max_arg == -math.inf: 

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

450 raise InvalidArgument( 

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

452 "but allow_infinity=False" 

453 ) 

454 

455 smallest_normal = width_smallest_normals[width] 

456 if allow_subnormal is None: 

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

458 if min_value == max_value: 

459 allow_subnormal = -smallest_normal < min_value < smallest_normal 

460 else: 

461 allow_subnormal = ( 

462 min_value < smallest_normal and max_value > -smallest_normal 

463 ) 

464 elif min_value is not None: 

465 allow_subnormal = min_value < smallest_normal 

466 elif max_value is not None: 

467 allow_subnormal = max_value > -smallest_normal 

468 else: 

469 allow_subnormal = True 

470 if allow_subnormal: 

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

472 raise InvalidArgument( 

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

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

475 f"smallest positive normal {smallest_normal}" 

476 ) 

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

478 raise InvalidArgument( 

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

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

481 f"smallest negative normal {-smallest_normal}" 

482 ) 

483 

484 if min_value is None: 

485 min_value = float("-inf") 

486 if max_value is None: 

487 max_value = float("inf") 

488 if not allow_infinity: 

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

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

491 assert isinstance(min_value, float) 

492 assert isinstance(max_value, float) 

493 smallest_nonzero_magnitude = ( 

494 SMALLEST_SUBNORMAL if allow_subnormal else smallest_normal 

495 ) 

496 result: SearchStrategy = FloatStrategy( 

497 min_value=min_value, 

498 max_value=max_value, 

499 allow_nan=allow_nan, 

500 smallest_nonzero_magnitude=smallest_nonzero_magnitude, 

501 ) 

502 

503 if width < 64: 

504 

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

506 try: 

507 return float_of(x, width) 

508 except OverflowError: # pragma: no cover 

509 reject() 

510 

511 result = result.map(downcast) 

512 return result 

513 

514 

515class NanStrategy(SearchStrategy[float]): 

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

517 

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

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

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

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

522 nan_bits = float_to_int(math.nan) 

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

524 return int_to_float(sign_bit | nan_bits | mantissa_bits)