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))