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