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

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

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