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

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

100 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 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