Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/internal/conjecture/shrinking/floats.py: 32%

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

40 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 

12import sys 

13 

14from hypothesis.internal.conjecture.floats import float_to_lex 

15from hypothesis.internal.conjecture.shrinking.common import Shrinker 

16from hypothesis.internal.conjecture.shrinking.integer import Integer 

17from hypothesis.internal.floats import MAX_PRECISE_INTEGER, float_to_int 

18 

19 

20class Float(Shrinker): 

21 def setup(self): 

22 self.debugging_enabled = True 

23 

24 def make_canonical(self, f): 

25 if math.isnan(f): 

26 # Distinguish different NaN bit patterns, while making each equal to itself. 

27 # Wrap in tuple to avoid potential collision with (huge) finite floats. 

28 return ("nan", float_to_int(f)) 

29 return f 

30 

31 def check_invariants(self, value): 

32 # We only handle positive floats (including NaN) because we encode the sign 

33 # separately anyway. 

34 assert not (value < 0) 

35 

36 def left_is_better(self, left, right): 

37 lex1 = float_to_lex(left) 

38 lex2 = float_to_lex(right) 

39 return lex1 < lex2 

40 

41 def short_circuit(self): 

42 # We check for a bunch of standard "large" floats. If we're currently 

43 # worse than them and the shrink downwards doesn't help, abort early 

44 # because there's not much useful we can do here. 

45 

46 for g in [sys.float_info.max, math.inf, math.nan]: 

47 self.consider(g) 

48 

49 # If we're stuck at a nasty float don't try to shrink it further. 

50 if not math.isfinite(self.current): 

51 return True 

52 

53 def run_step(self): 

54 # above MAX_PRECISE_INTEGER, all floats are integers. Shrink like one. 

55 # TODO_BETTER_SHRINK: at 2 * MAX_PRECISE_INTEGER, n - 1 == n - 2, and 

56 # Integer.shrink will likely perform badly. We should have a specialized 

57 # big-float shrinker, which mostly follows Integer.shrink but replaces 

58 # n - 1 with next_down(n). 

59 if self.current > MAX_PRECISE_INTEGER: 

60 self.delegate(Integer, convert_to=int, convert_from=float) 

61 return 

62 

63 # Finally we get to the important bit: Each of these is a small change 

64 # to the floating point number that corresponds to a large change in 

65 # the lexical representation. Trying these ensures that our floating 

66 # point shrink can always move past these obstacles. In particular it 

67 # ensures we can always move to integer boundaries and shrink past a 

68 # change that would require shifting the exponent while not changing 

69 # the float value much. 

70 

71 # First, try dropping precision bits by rounding the scaled value. We 

72 # try values ordered from least-precise (integer) to more precise, ie. 

73 # approximate lexicographical order. Once we find an acceptable shrink, 

74 # self.consider discards the remaining attempts early and skips test 

75 # invocation. The loop count sets max fractional bits to keep, and is a 

76 # compromise between completeness and performance. 

77 

78 for p in range(10): 

79 scaled = self.current * 2**p # note: self.current may change in loop 

80 for truncate in [math.floor, math.ceil]: 

81 self.consider(truncate(scaled) / 2**p) 

82 

83 if self.consider(int(self.current)): 

84 self.debug("Just an integer now") 

85 self.delegate(Integer, convert_to=int, convert_from=float) 

86 return 

87 

88 # Now try to minimize the top part of the fraction as an integer. This 

89 # basically splits the float as k + x with 0 <= x < 1 and minimizes 

90 # k as an integer, but without the precision issues that would have. 

91 m, n = self.current.as_integer_ratio() 

92 i, r = divmod(m, n) 

93 self.call_shrinker(Integer, i, lambda k: self.consider((k * n + r) / n))