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 Callable, Optional, SupportsFloat
15
16# Format codes for (int, float) sized types, used for byte-wise casts.
17# See https://docs.python.org/3/library/struct.html#format-characters
18STRUCT_FORMATS = {
19 16: ("!H", "!e"),
20 32: ("!I", "!f"),
21 64: ("!Q", "!d"),
22}
23
24
25def reinterpret_bits(x, from_, to):
26 return struct.unpack(to, struct.pack(from_, x))[0]
27
28
29def float_of(x, width):
30 assert width in (16, 32, 64)
31 if width == 64:
32 return float(x)
33 elif width == 32:
34 return reinterpret_bits(float(x), "!f", "!f")
35 else:
36 return reinterpret_bits(float(x), "!e", "!e")
37
38
39def is_negative(x: SupportsFloat) -> bool:
40 try:
41 return math.copysign(1.0, x) < 0
42 except TypeError:
43 raise TypeError(
44 f"Expected float but got {x!r} of type {type(x).__name__}"
45 ) from None
46
47
48def count_between_floats(x, y, width=64):
49 assert x <= y
50 if is_negative(x):
51 if is_negative(y):
52 return float_to_int(x, width) - float_to_int(y, width) + 1
53 else:
54 return count_between_floats(x, -0.0, width) + count_between_floats(
55 0.0, y, width
56 )
57 else:
58 assert not is_negative(y)
59 return float_to_int(y, width) - float_to_int(x, width) + 1
60
61
62def float_to_int(value, width=64):
63 fmt_int, fmt_flt = STRUCT_FORMATS[width]
64 return reinterpret_bits(value, fmt_flt, fmt_int)
65
66
67def int_to_float(value, width=64):
68 fmt_int, fmt_flt = STRUCT_FORMATS[width]
69 return reinterpret_bits(value, fmt_int, fmt_flt)
70
71
72def next_up(value, width=64):
73 """Return the first float larger than finite `val` - IEEE 754's `nextUp`.
74
75 From https://stackoverflow.com/a/10426033, with thanks to Mark Dickinson.
76 """
77 assert isinstance(value, float), f"{value!r} of type {type(value)}"
78 if math.isnan(value) or (math.isinf(value) and value > 0):
79 return value
80 if value == 0.0 and is_negative(value):
81 return 0.0
82 fmt_int, fmt_flt = STRUCT_FORMATS[width]
83 # Note: n is signed; float_to_int returns unsigned
84 fmt_int = fmt_int.lower()
85 n = reinterpret_bits(value, fmt_flt, fmt_int)
86 if n >= 0:
87 n += 1
88 else:
89 n -= 1
90 return reinterpret_bits(n, fmt_int, fmt_flt)
91
92
93def next_down(value, width=64):
94 return -next_up(-value, width)
95
96
97def next_down_normal(value, width, allow_subnormal):
98 value = next_down(value, width)
99 if (not allow_subnormal) and 0 < abs(value) < width_smallest_normals[width]:
100 return 0.0 if value > 0 else -width_smallest_normals[width]
101 return value
102
103
104def next_up_normal(value, width, allow_subnormal):
105 return -next_down_normal(-value, width, allow_subnormal)
106
107
108# Smallest positive non-zero numbers that is fully representable by an
109# IEEE-754 float, calculated with the width's associated minimum exponent.
110# Values from https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
111width_smallest_normals = {
112 16: 2 ** -(2 ** (5 - 1) - 2),
113 32: 2 ** -(2 ** (8 - 1) - 2),
114 64: 2 ** -(2 ** (11 - 1) - 2),
115}
116assert width_smallest_normals[64] == float_info.min
117
118
119def make_float_clamper(
120 min_float: float = 0.0,
121 max_float: float = math.inf,
122 *,
123 allow_zero: bool = False, # Allows +0.0 (even if minfloat > 0)
124) -> Optional[Callable[[float], float]]:
125 """
126 Return a function that clamps positive floats into the given bounds.
127
128 Returns None when no values are allowed (min > max and zero is not allowed).
129 """
130 if max_float < min_float:
131 if allow_zero:
132 min_float = max_float = 0.0
133 else:
134 return None
135
136 range_size = min(max_float - min_float, float_info.max)
137 mantissa_mask = (1 << 52) - 1
138
139 def float_clamper(float_val: float) -> float:
140 if min_float <= float_val <= max_float:
141 return float_val
142 if float_val == 0.0 and allow_zero:
143 return float_val
144 # Outside bounds; pick a new value, sampled from the allowed range,
145 # using the mantissa bits.
146 mant = float_to_int(float_val) & mantissa_mask
147 float_val = min_float + range_size * (mant / mantissa_mask)
148 # Re-enforce the bounds (just in case of floating point arithmetic error)
149 return max(min_float, min(max_float, float_val))
150
151 return float_clamper
152
153
154def sign_aware_lte(x: float, y: float) -> bool:
155 """Less-than-or-equals, but strictly orders -0.0 and 0.0"""
156 if x == 0.0 == y:
157 return math.copysign(1.0, x) <= math.copysign(1.0, y)
158 else:
159 return x <= y
160
161
162SMALLEST_SUBNORMAL = next_up(0.0)
163SIGNALING_NAN = int_to_float(0x7FF8_0000_0000_0001) # nonzero mantissa
164assert math.isnan(SIGNALING_NAN)
165assert math.copysign(1, SIGNALING_NAN) == 1