125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
# This file is part of Hypothesis, which may be found at
|
|
# https://github.com/HypothesisWorks/hypothesis/
|
|
#
|
|
# Most of this work is copyright (C) 2013-2021 David R. MacIver
|
|
# (david@drmaciver.com), but it contains contributions by others. See
|
|
# CONTRIBUTING.rst for a full list of people who may hold copyright, and
|
|
# consult the git log if you need to determine who owns an individual
|
|
# contribution.
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public License,
|
|
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
|
# obtain one at https://mozilla.org/MPL/2.0/.
|
|
#
|
|
# END HEADER
|
|
|
|
import math
|
|
import struct
|
|
from sys import float_info
|
|
|
|
# Format codes for (int, float) sized types, used for byte-wise casts.
|
|
# See https://docs.python.org/3/library/struct.html#format-characters
|
|
STRUCT_FORMATS = {
|
|
16: ("!H", "!e"),
|
|
32: ("!I", "!f"),
|
|
64: ("!Q", "!d"),
|
|
}
|
|
|
|
|
|
def reinterpret_bits(x, from_, to):
|
|
return struct.unpack(to, struct.pack(from_, x))[0]
|
|
|
|
|
|
def float_of(x, width):
|
|
assert width in (16, 32, 64)
|
|
if width == 64:
|
|
return float(x)
|
|
elif width == 32:
|
|
return reinterpret_bits(float(x), "!f", "!f")
|
|
else:
|
|
return reinterpret_bits(float(x), "!e", "!e")
|
|
|
|
|
|
def sign(x):
|
|
try:
|
|
return math.copysign(1.0, x)
|
|
except TypeError:
|
|
raise TypeError(
|
|
f"Expected float but got {x!r} of type {type(x).__name__}"
|
|
) from None
|
|
|
|
|
|
def is_negative(x):
|
|
return sign(x) < 0
|
|
|
|
|
|
def count_between_floats(x, y, width=64):
|
|
assert x <= y
|
|
if is_negative(x):
|
|
if is_negative(y):
|
|
return float_to_int(x, width) - float_to_int(y, width) + 1
|
|
else:
|
|
return count_between_floats(x, -0.0, width) + count_between_floats(
|
|
0.0, y, width
|
|
)
|
|
else:
|
|
assert not is_negative(y)
|
|
return float_to_int(y, width) - float_to_int(x, width) + 1
|
|
|
|
|
|
def float_to_int(value, width=64):
|
|
fmt_int, fmt_flt = STRUCT_FORMATS[width]
|
|
return reinterpret_bits(value, fmt_flt, fmt_int)
|
|
|
|
|
|
def int_to_float(value, width=64):
|
|
fmt_int, fmt_flt = STRUCT_FORMATS[width]
|
|
return reinterpret_bits(value, fmt_int, fmt_flt)
|
|
|
|
|
|
def next_up(value, width=64):
|
|
"""Return the first float larger than finite `val` - IEEE 754's `nextUp`.
|
|
|
|
From https://stackoverflow.com/a/10426033, with thanks to Mark Dickinson.
|
|
"""
|
|
assert isinstance(value, float), f"{value!r} of type {type(value)}"
|
|
if math.isnan(value) or (math.isinf(value) and value > 0):
|
|
return value
|
|
if value == 0.0 and is_negative(value):
|
|
return 0.0
|
|
fmt_int, fmt_flt = STRUCT_FORMATS[width]
|
|
# Note: n is signed; float_to_int returns unsigned
|
|
fmt_int = fmt_int.lower()
|
|
n = reinterpret_bits(value, fmt_flt, fmt_int)
|
|
if n >= 0:
|
|
n += 1
|
|
else:
|
|
n -= 1
|
|
return reinterpret_bits(n, fmt_int, fmt_flt)
|
|
|
|
|
|
def next_down(value, width=64):
|
|
return -next_up(-value, width)
|
|
|
|
|
|
def next_down_normal(value, width, allow_subnormal):
|
|
value = next_down(value, width)
|
|
if (not allow_subnormal) and 0 < abs(value) < width_smallest_normals[width]:
|
|
return 0.0 if value > 0 else -width_smallest_normals[width]
|
|
return value
|
|
|
|
|
|
def next_up_normal(value, width, allow_subnormal):
|
|
return -next_down_normal(-value, width, allow_subnormal)
|
|
|
|
|
|
# Smallest positive non-zero numbers that is fully representable by an
|
|
# IEEE-754 float, calculated with the width's associated minimum exponent.
|
|
# Values from https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats
|
|
width_smallest_normals = {
|
|
16: 2 ** -(2 ** (5 - 1) - 2),
|
|
32: 2 ** -(2 ** (8 - 1) - 2),
|
|
64: 2 ** -(2 ** (11 - 1) - 2),
|
|
}
|
|
assert width_smallest_normals[64] == float_info.min
|