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