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 struct
13from sys import float_info
14from typing import TYPE_CHECKING, Callable, Literal, SupportsFloat, Union
15
16if TYPE_CHECKING:
17 from typing import TypeAlias
18
19SignedIntFormat: "TypeAlias" = Literal["!h", "!i", "!q"]
20UnsignedIntFormat: "TypeAlias" = Literal["!H", "!I", "!Q"]
21IntFormat: "TypeAlias" = Union[SignedIntFormat, UnsignedIntFormat]
22FloatFormat: "TypeAlias" = Literal["!e", "!f", "!d"]
23Width: "TypeAlias" = Literal[16, 32, 64]
24
25# Format codes for (int, float) sized types, used for byte-wise casts.
26# See https://docs.python.org/3/library/struct.html#format-characters
27STRUCT_FORMATS: dict[int, tuple[UnsignedIntFormat, FloatFormat]] = {
28 16: ("!H", "!e"),
29 32: ("!I", "!f"),
30 64: ("!Q", "!d"),
31}
32
33TO_SIGNED_FORMAT: dict[UnsignedIntFormat, SignedIntFormat] = {
34 "!H": "!h",
35 "!I": "!i",
36 "!Q": "!q",
37}
38
39
40def reinterpret_bits(x: float, from_: str, to: str) -> float:
41 x = struct.unpack(to, struct.pack(from_, x))[0]
42 assert isinstance(x, (float, int))
43 return x
44
45
46def float_of(x: SupportsFloat, width: Width) -> float:
47 assert width in (16, 32, 64)
48 if width == 64:
49 return float(x)
50 elif width == 32:
51 return reinterpret_bits(float(x), "!f", "!f")
52 else:
53 return reinterpret_bits(float(x), "!e", "!e")
54
55
56def is_negative(x: SupportsFloat) -> bool:
57 try:
58 return math.copysign(1.0, x) < 0
59 except TypeError:
60 raise TypeError(
61 f"Expected float but got {x!r} of type {type(x).__name__}"
62 ) from None
63
64
65def count_between_floats(x: float, y: float, width: int = 64) -> int:
66 assert x <= y
67 if is_negative(x):
68 if is_negative(y):
69 return float_to_int(x, width) - float_to_int(y, width) + 1
70 else:
71 return count_between_floats(x, -0.0, width) + count_between_floats(
72 0.0, y, width
73 )
74 else:
75 assert not is_negative(y)
76 return float_to_int(y, width) - float_to_int(x, width) + 1
77
78
79def float_to_int(value: float, width: int = 64) -> int:
80 fmt_int, fmt_flt = STRUCT_FORMATS[width]
81 x = reinterpret_bits(value, fmt_flt, fmt_int)
82 assert isinstance(x, int)
83 return x
84
85
86def int_to_float(value: int, width: int = 64) -> float:
87 fmt_int, fmt_flt = STRUCT_FORMATS[width]
88 return reinterpret_bits(value, fmt_int, fmt_flt)
89
90
91def next_up(value: float, width: int = 64) -> float:
92 """Return the first float larger than finite `val` - IEEE 754's `nextUp`.
93
94 From https://stackoverflow.com/a/10426033, with thanks to Mark Dickinson.
95 """
96 assert isinstance(value, float), f"{value!r} of type {type(value)}"
97 if math.isnan(value) or (math.isinf(value) and value > 0):
98 return value
99 if value == 0.0 and is_negative(value):
100 return 0.0
101 fmt_int, fmt_flt = STRUCT_FORMATS[width]
102 # Note: n is signed; float_to_int returns unsigned
103 fmt_int_signed = TO_SIGNED_FORMAT[fmt_int]
104 n = reinterpret_bits(value, fmt_flt, fmt_int_signed)
105 if n >= 0:
106 n += 1
107 else:
108 n -= 1
109 return reinterpret_bits(n, fmt_int_signed, fmt_flt)
110
111
112def next_down(value: float, width: int = 64) -> float:
113 return -next_up(-value, width)
114
115
116def next_down_normal(value: float, width: int, *, allow_subnormal: bool) -> float:
117 value = next_down(value, width)
118 if (not allow_subnormal) and 0 < abs(value) < width_smallest_normals[width]:
119 return 0.0 if value > 0 else -width_smallest_normals[width]
120 return value
121
122
123def next_up_normal(value: float, width: int, *, allow_subnormal: bool) -> float:
124 return -next_down_normal(-value, width, allow_subnormal=allow_subnormal)
125
126
127# Smallest positive non-zero numbers that is fully representable by an
128# IEEE-754 float, calculated with the width's associated minimum exponent.
129# Values from https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
130width_smallest_normals: dict[int, float] = {
131 16: 2 ** -(2 ** (5 - 1) - 2),
132 32: 2 ** -(2 ** (8 - 1) - 2),
133 64: 2 ** -(2 ** (11 - 1) - 2),
134}
135assert width_smallest_normals[64] == float_info.min
136
137mantissa_mask = (1 << 52) - 1
138
139
140def make_float_clamper(
141 min_value: float,
142 max_value: float,
143 *,
144 allow_nan: bool,
145 smallest_nonzero_magnitude: float,
146) -> Callable[[float], float]:
147 """
148 Return a function that clamps positive floats into the given bounds.
149 """
150 from hypothesis.internal.conjecture.choice import choice_permitted
151
152 assert sign_aware_lte(min_value, max_value)
153 range_size = min(max_value - min_value, float_info.max)
154
155 def float_clamper(f: float) -> float:
156 if choice_permitted(
157 f,
158 {
159 "min_value": min_value,
160 "max_value": max_value,
161 "allow_nan": allow_nan,
162 "smallest_nonzero_magnitude": smallest_nonzero_magnitude,
163 },
164 ):
165 return f
166 # Outside bounds; pick a new value, sampled from the allowed range,
167 # using the mantissa bits.
168 mant = float_to_int(abs(f)) & mantissa_mask
169 f = min_value + range_size * (mant / mantissa_mask)
170
171 # if we resampled into the space disallowed by smallest_nonzero_magnitude,
172 # default to smallest_nonzero_magnitude.
173 if 0 < abs(f) < smallest_nonzero_magnitude:
174 f = smallest_nonzero_magnitude
175 # we must have either -smallest_nonzero_magnitude <= min_value or
176 # smallest_nonzero_magnitude >= max_value, or no values would be
177 # possible. If smallest_nonzero_magnitude is not valid (because it's
178 # larger than max_value), then -smallest_nonzero_magnitude must be valid.
179 if smallest_nonzero_magnitude > max_value:
180 f *= -1
181
182 # Re-enforce the bounds (just in case of floating point arithmetic error)
183 return clamp(min_value, f, max_value)
184
185 return float_clamper
186
187
188def sign_aware_lte(x: float, y: float) -> bool:
189 """Less-than-or-equals, but strictly orders -0.0 and 0.0"""
190 if x == 0.0 == y:
191 return math.copysign(1.0, x) <= math.copysign(1.0, y)
192 else:
193 return x <= y
194
195
196def clamp(lower: float, value: float, upper: float) -> float:
197 """Given a value and lower/upper bounds, 'clamp' the value so that
198 it satisfies lower <= value <= upper. NaN is mapped to lower."""
199 # this seems pointless (and is for integers), but handles the -0.0/0.0 case.
200 if not sign_aware_lte(lower, value):
201 return lower
202 if not sign_aware_lte(value, upper):
203 return upper
204 return value
205
206
207SMALLEST_SUBNORMAL = next_up(0.0)
208SIGNALING_NAN = int_to_float(0x7FF8_0000_0000_0001) # nonzero mantissa
209MAX_PRECISE_INTEGER = 2**53
210assert math.isnan(SIGNALING_NAN)
211assert math.copysign(1, SIGNALING_NAN) == 1