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

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

102 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 

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