Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/internal/floats.py: 70%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

101 statements  

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