Merge branch 'master' into header

This commit is contained in:
Robert Smallshire
2015-04-17 12:20:13 +02:00
9 changed files with 701 additions and 42 deletions
+48
View File
@@ -0,0 +1,48 @@
def lfu_cache(maxsize=100):
'''Least-frequently-used cache decorator.
Arguments to the cached function must be hashable.
Cache performance statistics stored in f.hits and f.misses.
Clear the cache with f.clear().
http://en.wikipedia.org/wiki/Least_Frequently_Used
'''
def decorating_function(user_function):
cache = {} # mapping of args to results
use_count = Counter() # times each key has been accessed
kwd_mark = object() # separate positional and keyword args
@functools.wraps(user_function)
def wrapper(*args, **kwds):
key = args
if kwds:
key += (kwd_mark,) + tuple(sorted(kwds.items()))
use_count[key] += 1
# get cache entry or compute if not found
try:
result = cache[key]
wrapper.hits += 1
except KeyError:
result = user_function(*args, **kwds)
cache[key] = result
wrapper.misses += 1
# purge least frequently used cache entry
if len(cache) > maxsize:
for key, _ in nsmallest(maxsize // 10,
use_count.iteritems(),
key=itemgetter(1)):
del cache[key], use_count[key]
return result
def clear():
cache.clear()
use_count.clear()
wrapper.hits = wrapper.misses = 0
wrapper.hits = wrapper.misses = 0
wrapper.clear = clear
return wrapper
return decorating_function
+384 -7
View File
@@ -1,15 +1,31 @@
from math import frexp, isnan, isinf
from math import frexp, isnan, isinf, ceil, floor, trunc
from numbers import Real
from segpy.portability import long_int, byte_string, four_bytes
IBM_ZERO_BYTES = b'\x00\x00\x00\x00'
IBM_NEGATIVE_ONE_BYTES = b'\xc1\x10\x00\x00'
IBM_POSITIVE_ONE_BYTES = b'A\x10\x00\x00'
MIN_IBM_FLOAT = -7.2370051459731155e+75
LARGEST_NEGATIVE_NORMAL_IBM_FLOAT = -5.397605346934028e-79
SMALLEST_POSITIVE_NORMAL_IBM_FLOAT = 5.397605346934028e-79
MAX_IBM_FLOAT = 7.2370051459731155e+75
_IBM_FLOAT32_BITS_PRECISION = 24
_L24 = long_int(2) ** _IBM_FLOAT32_BITS_PRECISION
_F24 = float(pow(2, _IBM_FLOAT32_BITS_PRECISION))
MAX_BITS_PRECISION_IBM_FLOAT = 24
MIN_BITS_PRECISION_IBM_FLOAT = 21 # The first 3 bits of the mantissa may be zero
EPSILON_IBM_FLOAT = pow(2.0, -(MIN_BITS_PRECISION_IBM_FLOAT - 1))
_L24 = long_int(2) ** MAX_BITS_PRECISION_IBM_FLOAT
_F24 = float(pow(2, MAX_BITS_PRECISION_IBM_FLOAT))
_L21 = long_int(2) ** MIN_BITS_PRECISION_IBM_FLOAT
EXPONENT_BIAS = 64
MIN_EXACT_INTEGER_IBM_FLOAT = -2**MAX_BITS_PRECISION_IBM_FLOAT
MAX_EXACT_INTEGER_IBM_FLOAT = 2**MIN_BITS_PRECISION_IBM_FLOAT
def ibm2ieee(big_endian_bytes):
@@ -27,15 +43,55 @@ def ibm2ieee(big_endian_bytes):
return 0.0
sign = -1 if (a & 0x80) else 1
exponent = a & 0x7f
exponent_16_biased = a & 0x7f
mantissa = ((b << 16) | (c << 8) | d) / _F24
value = sign * mantissa * pow(16, exponent - 64)
value = sign * mantissa * pow(16, exponent_16_biased - EXPONENT_BIAS)
return value
BITS_PER_NYBBLE = 4
def truncate(big_endian_bytes):
a, b, c, d = four_bytes(big_endian_bytes)
sign = -1 if (a & 0x80) else 1
exponent_16_biased = a & 0x7f
exponent_16 = exponent_16_biased - EXPONENT_BIAS
mantissa = ((b << 16) | (c << 8) | d)
print("sign =", sign)
print("exponent_16", exponent_16, hex(exponent_16), bin(exponent_16))
print("mantissa", mantissa, hex(mantissa), bin(mantissa))
num_nybbles_to_preserve = min(exponent_16, MAX_BITS_PRECISION_IBM_FLOAT // BITS_PER_NYBBLE)
num_bits_to_clear = MAX_BITS_PRECISION_IBM_FLOAT - num_nybbles_to_preserve * BITS_PER_NYBBLE
clear_mask = 2**num_bits_to_clear - 1
preserve_mask = (2**MAX_BITS_PRECISION_IBM_FLOAT - 1) & ~clear_mask
print("num_nybbles_to_preserve", num_nybbles_to_preserve)
print("num_bits_to_clear", num_bits_to_clear)
print("clear_mask ", bin(clear_mask))
print("preserve_mask", bin(preserve_mask))
truncated_mantissa = mantissa & preserve_mask
value = truncated_mantissa * pow(16, exponent_16)
scaled_value = value >> MAX_BITS_PRECISION_IBM_FLOAT
return scaled_value
#
# tb = (preserve_mask >> 16) & b
# tc = (preserve_mask >> 8) & c
# td = preserve_mask & d
#
# return byte_string((a, tb, tc, td))
def ieee2ibm(f):
"""Covert a float to four big-endian bytes representing an IBM float.
"""Convert a float to four big-endian bytes representing an IBM float.
Args:
f (float): The value to be converted.
@@ -112,3 +168,324 @@ def ieee2ibm(f):
d = mantissa & 0xff
return byte_string((a, b, c, d))
class IBMFloat(Real):
__slots__ = ['_data']
_INTERNED = {IBM_ZERO_BYTES: None,
IBM_NEGATIVE_ONE_BYTES: None,
IBM_POSITIVE_ONE_BYTES: None}
# noinspection PyUnresolvedReferences
def __new__(cls, b):
obj = object.__new__(cls)
data = bytes(b)
num_bytes = len(data)
if num_bytes != 4:
raise ValueError("{} cannot be constructed from {} values".format(cls.__name__, num_bytes))
obj._data = data
# Intern common values
if data in cls._INTERNED:
if cls._INTERNED[data] is None:
cls._INTERNED[data] = obj
return cls._INTERNED[data]
return obj
@classmethod
def from_float(cls, f):
"""Construct an IBMFloat from an IEEE float.
Args:
f (float): The value to be converted.
Returns:
An IBMFloat.
Raises:
OverflowError: If f is outside the representable range.
ValueError: If f is NaN or infinite.
FloatingPointError: If f cannot be represented without total loss of precision.
"""
return cls(ieee2ibm(f))
@classmethod
def from_real(cls, f):
if isinstance(f, IBMFloat):
return f
return cls.from_float(f)
@classmethod
def from_bytes(cls, b):
return cls(b)
@classmethod
def ldexp(cls, fraction, exponent):
"""Make an IBMFloat from fraction and exponent.
The is the inverse function of IBMFloat.frexp()
Args:
fraction: A Real in the range -1.0 to 1.0.
exponent: An integer in the range -256 to 255 inclusive.
"""
if not (-1.0 <= fraction <= 1.0):
raise ValueError("ldexp fraction {!r} out of range -1.0 to +1.0")
if not (-256 <= exponent < 256):
raise ValueError("ldexp exponent {!r} out of range -256 to 256")
ieee = fraction * 2**exponent
return IBMFloat.from_float(ieee)
@property
def signbit(self):
"""True if the value is negative, otherwise False."""
return bool(self._data[0] & 0x80)
def __float__(self):
return ibm2ieee(self._data)
def __bytes__(self):
return self._data
def __repr__(self):
return "{}.from_float({!r}) ~{!r}".format(self.__class__.__name__, self._data, float(self))
def __str__(self):
return str(float(self))
def __bool__(self):
return not self.is_zero()
def is_zero(self):
return self.int_mantissa == 0
def __nonzero__(self):
return not self.is_zero()
def is_subnormal(self):
if self.is_zero():
# Only one of the many possible representations of zero is considered 'normal' - all the zeros
return not all(b == 0 for b in self._data)
return self._data[1] < 16 # TODO: Replace magic number with constant
def zero_subnormal(self):
return IBM_FLOAT_ZERO if self.is_subnormal() else self
def frexp(self):
"""Obtain the fraction and exponent.
Returns:
A pair where the first item is the fraction in the range -1.0 and +1.0 and the
exponent is an integer such that f = fraction * 2**exponent
"""
sign = -1 if self.signbit else 1
mantissa = sign * self.int_mantissa / _F24
exp_2 = self.exp16 * 4
return mantissa, exp_2
def __pos__(self):
return self
def __neg__(self):
if self.is_zero():
return IBM_FLOAT_ZERO
data = self._data
return IBMFloat((data[0] ^ 0b10000000,
data[1],
data[2],
data[3]))
def __abs__(self):
if self.is_zero():
return IBM_FLOAT_ZERO
data = self._data
return IBMFloat((data[0] & 0b01111111,
data[1],
data[2],
data[3]))
def __eq__(self, rhs):
lhs = self
if not isinstance(rhs, IBMFloat):
return float(lhs) == float(rhs)
lhs_sign = lhs.signbit
rhs_sign = rhs.signbit
if lhs_sign != rhs_sign:
return False
nlhs = lhs.normalize()
nrhs = rhs.normalize()
if not (nlhs.is_subnormal() or nrhs.is_subnormal()):
# Both of the numbers are normalised
return nlhs._data == nrhs._data
# Either or both of the numbers are subnormal
lhs_exp16 = nlhs.exp16
rhs_exp16 = nrhs.exp16
lhs_mantissa = nlhs.int_mantissa
rhs_mantissa = nrhs.int_mantissa
if lhs_exp16 < rhs_exp16:
delta_exp16 = rhs_exp16 - lhs_exp16
lhs_mantissa >>= 4 * delta_exp16
lhs_exp16 += delta_exp16
if lhs_exp16 > rhs_exp16:
delta_exp16 = lhs_exp16 - rhs_exp16
rhs_mantissa >>= 4 * delta_exp16
rhs_exp16 += delta_exp16
assert lhs_exp16 == rhs_exp16
return lhs_mantissa == rhs_mantissa
def __floordiv__(self, rhs):
return float(self) // float(rhs)
def __rfloordiv__(self, lhs):
return float(lhs) // float(self)
def __rtruediv__(self, lhs):
q = float(lhs) / float(self)
return IBMFloat.from_float(q) if isinstance(lhs, float) else q
def __pow__(self, exponent):
p = pow(float(self), float(exponent))
return IBMFloat.from_float(p) if isinstance(exponent, IBMFloat) else p
def __rpow__(self, base):
return IBMFloat.from_float(pow(float(base), float(self)))
def __mod__(self, rhs):
m = float(self) % float(rhs)
return IBMFloat.from_float(m) if isinstance(rhs, IBMFloat) else m
def __rmod__(self, lhs):
m = float(lhs) % float(self)
return IBMFloat.from_float(m) if isinstance(lhs, IBMFloat) else m
def __rmul__(self, lhs):
p = float(lhs) * float(self)
return IBMFloat.from_float(p) if isinstance(lhs, IBMFloat) else p
def __radd__(self, lhs):
s = float(lhs) + float(self)
return IBMFloat.from_float(s) if isinstance(lhs, IBMFloat) else s
def __lt__(self, rhs):
return float(self) < float(rhs)
def __le__(self, rhs):
return float(self) <= float(rhs)
def __gt__(self, rhs):
return float(self) > float(rhs)
def __ge__(self, rhs):
return float(self) >= float(rhs)
def __ceil__(self):
t = trunc(self)
return t if self.signbit else t + 1
def __floor__(self):
t = trunc(self)
return t - 1 if self.signbit else t
@property
def exp16(self):
"""The base 16 exponent."""
exponent_16_biased = self._data[0] & 0x7f
exponent_16 = exponent_16_biased - EXPONENT_BIAS
return exponent_16
@property
def int_mantissa(self):
data = self._data
return (data[1] << 16) | (data[2] << 8) | data[3]
def __trunc__(self):
sign = -1 if self.signbit else 1
exponent_16 = self.exp16
mantissa = self.int_mantissa
num_nybbles_to_preserve = min(exponent_16, MAX_BITS_PRECISION_IBM_FLOAT // BITS_PER_NYBBLE)
num_bits_to_clear = MAX_BITS_PRECISION_IBM_FLOAT - num_nybbles_to_preserve * BITS_PER_NYBBLE
clear_mask = 2**num_bits_to_clear - 1
preserve_mask = (2**MAX_BITS_PRECISION_IBM_FLOAT - 1) & ~clear_mask
truncated_mantissa = mantissa & preserve_mask
magnitude = truncated_mantissa * pow(16, exponent_16) >> MAX_BITS_PRECISION_IBM_FLOAT
return sign * magnitude
def normalize(self):
"""Normalize the floating point value.
Returns:
A normalized IBMFloat equal in value to this object.
Raises:
FloatingPointError: If the number could not be normalized.
"""
if self.is_zero():
return IBM_FLOAT_ZERO
exponent_16 = self.exp16
mantissa = self.int_mantissa
while mantissa < (1 << 20):
new_exponent_16 = exponent_16 - 1
if not (-64 <= new_exponent_16 < 64):
raise FloatingPointError("Could not normalize {!r} without causing exponent overflow.".format(self))
mantissa <<= 4
exponent_16 = new_exponent_16
exponent_16_biased = exponent_16 + EXPONENT_BIAS
sign = int(self.signbit) << 7
a = sign | exponent_16_biased
b = (mantissa >> 16) & 0xff
c = (mantissa >> 8) & 0xff
d = mantissa & 0xff
return IBMFloat.from_bytes((a, b, c, d))
def __round__(self, ndigits=None):
return IBMFloat.from_float(round(float(self), ndigits))
def __truediv__(self, rhs):
q = float(self) / float(rhs)
return IBMFloat.from_float(q) if isinstance(rhs, IBMFloat) else q
def __mul__(self, rhs):
p = float(self) * float(rhs)
return IBMFloat.from_float(p) if isinstance(rhs, IBMFloat) else p
def __add__(self, rhs):
p = float(self) + float(rhs)
return IBMFloat.from_float(p) if isinstance(rhs, IBMFloat) else p
def __int__(self):
return trunc(self)
IBM_FLOAT_ZERO = IBMFloat.from_bytes(IBM_ZERO_BYTES)
+3 -3
View File
@@ -15,7 +15,7 @@ from segpy.catalog import CatalogBuilder
from segpy.datatypes import CTYPES, size_in_bytes
from segpy.encoding import guess_encoding, is_supported_encoding, UnsupportedEncodingError
from segpy.binary_reel_header_definition import HEADER_DEF
from segpy.ibm_float import ibm2ieee, ieee2ibm
from segpy.ibm_float import IBMFloat
from segpy.revisions import canonicalize_revision
from segpy.trace_header_definition import TRACE_HEADER_DEF
from segpy.util import file_length, batched, pad, complementary_intervals, NATIVE_ENDIANNESS
@@ -461,7 +461,7 @@ def unpack_ibm_floats(data, count):
Returns:
A sequence of floats.
"""
return array('d', (ibm2ieee(data[i: i+4]) for i in range(0, count * 4, 4)))
return [IBMFloat.from_bytes(data[i: i+4]) for i in range(0, count * 4, 4)]
def unpack_values(buf, count, fmt, endian='>'):
@@ -817,7 +817,7 @@ def pack_ibm_floats(values):
A sequence of bytes. (Python 2 - a str object, Python 3 - a bytes
object)
"""
return EMPTY_BYTE_STRING.join(ieee2ibm(value) for value in values)
return EMPTY_BYTE_STRING.join(bytes(IBMFloat.from_real(value)) for value in values)
def pack_values(values, fmt, endian='>'):
+8 -1
View File
@@ -240,4 +240,11 @@ def first_sentence(s):
def lower_first(s):
"""Lower case the first character of a string."""
return s[:1].lower() + s[1:]
return s[:1].lower() + s[1:]
def almost_equal(x, y, epsilon):
max_xy_one = max(1.0, abs(x), abs(y))
e = epsilon * max_xy_one
delta = abs(x - y)
return delta <= e
+21
View File
@@ -0,0 +1,21 @@
from hypothesis import strategy
from hypothesis.specifiers import integers_in_range
PRINTABLE_ASCII_RANGE = (32, 127)
def multiline_ascii_encodable_text(min_num_lines, max_num_lines):
"""A Hypothesis strategy to produce a multiline Unicode string.
Args:
min_num_lines: The minimum number of lines in the produced strings.
max_num_lines: The maximum number of lines in the produced strings.
Returns:
A strategy for generating Unicode strings containing only newlines
and characters which are encodable as printable 7-bit ASCII characters.
"""
return strategy(integers_in_range(0, 10)) \
.flatmap(lambda n: ([integers_in_range(*PRINTABLE_ASCII_RANGE)],) * n) \
.map(lambda xs: '\n'.join(bytes(x).decode('ascii') for x in xs))
+1 -1
View File
@@ -1 +1 @@
hypothesis==0.4.0
hypothesis>=1.2
+8 -28
View File
@@ -1,57 +1,37 @@
import unittest
from hypothesis import given
from hypothesis.descriptors import one_of, SampledFrom, Just, sampled_from, just
from hypothesis.searchstrategy import MappedSearchStrategy, StringStrategy
from hypothesis.strategytable import StrategyTable
from hypothesis.specifiers import sampled_from, just
from segpy.encoding import EBCDIC, ASCII
from segpy.toolkit import format_extended_textual_header, CARDS_PER_HEADER, END_TEXT_STANZA, CARD_LENGTH
from segpy.portability import unicode
class MultiLineString(unicode):
pass
class MultiLineStringStrategy(MappedSearchStrategy):
def pack(self, x):
return MultiLineString(unicode('\n').join(x))
def unpack(self, x):
return x.splitlines()
StrategyTable.default().define_specification_for(
MultiLineString,
lambda s, d: MultiLineStringStrategy(
strategy=s.strategy([unicode]),
descriptor=MultiLineString))
from test.strategies import multiline_ascii_encodable_text
class TestFormatExtendedTextualHeader(unittest.TestCase):
@given(MultiLineString,
@given(multiline_ascii_encodable_text(0, 100),
sampled_from([ASCII, EBCDIC]),
bool)
def test_forty_lines_per_page(self, text, encoding, include_text_stop):
pages = format_extended_textual_header(text, encoding, include_text_stop)
self.assertTrue(all(len(page) == CARDS_PER_HEADER for page in pages))
@given(MultiLineString,
@given(multiline_ascii_encodable_text(0, 100),
sampled_from([ASCII, EBCDIC]),
bool)
def test_eighty_bytes_per_encoded_line(self, text, encoding, include_text_stop):
pages = format_extended_textual_header(text, encoding, include_text_stop)
self.assertTrue(all([len(line.encode(encoding)) == CARD_LENGTH for page in pages for line in page]))
@given(MultiLineString,
@given(multiline_ascii_encodable_text(0, 100),
sampled_from([ASCII, EBCDIC]),
bool)
def test_lines_end_with_cr_lf(self, text, encoding, include_text_stop):
pages = format_extended_textual_header(text, encoding, include_text_stop)
self.assertTrue(all([line.endswith('\r\n') for page in pages for line in page]))
@given(MultiLineString,
@given(multiline_ascii_encodable_text(0, 100),
sampled_from([ASCII, EBCDIC]),
just(True))
def test_end_text_stanza_present(self, text, encoding, include_text_stop):
+227 -1
View File
@@ -1,8 +1,15 @@
from math import trunc, floor
import unittest
from hypothesis import given, assume
import math
from hypothesis.specifiers import integers_in_range, floats_in_range
from segpy.portability import byte_string
from segpy.ibm_float import (ieee2ibm, ibm2ieee, MAX_IBM_FLOAT, SMALLEST_POSITIVE_NORMAL_IBM_FLOAT,
LARGEST_NEGATIVE_NORMAL_IBM_FLOAT, MIN_IBM_FLOAT)
LARGEST_NEGATIVE_NORMAL_IBM_FLOAT, MIN_IBM_FLOAT, IBMFloat, EPSILON_IBM_FLOAT,
MAX_EXACT_INTEGER_IBM_FLOAT, MIN_EXACT_INTEGER_IBM_FLOAT, EXPONENT_BIAS)
from segpy.util import almost_equal
class Ibm2Ieee(unittest.TestCase):
@@ -134,5 +141,224 @@ class Ibm2IeeeRoundtrip(unittest.TestCase):
self.assertEqual(ibm_start, ibm_result)
class TestIBMFloat(unittest.TestCase):
def test_zero_from_float(self):
zero = IBMFloat.from_float(0.0)
self.assertTrue(zero.is_zero())
def test_zero_from_bytes(self):
zero = IBMFloat.from_bytes(b'\x00\x00\x00\x00')
self.assertTrue(zero.is_zero())
def test_subnormal(self):
ibm = IBMFloat.from_float(1.6472184286297693e-83)
self.assertTrue(ibm.is_subnormal())
def test_smallest_subnormal(self):
ibm = IBMFloat.from_float(5.147557589468029e-85)
self.assertEqual(bytes(ibm), byte_string((0x00, 0x00, 0x00, 0x01)))
def test_too_small_subnormal(self):
with self.assertRaises(FloatingPointError):
IBMFloat.from_float(1e-86)
def test_nan(self):
with self.assertRaises(ValueError):
IBMFloat.from_float(float('nan'))
def test_inf(self):
with self.assertRaises(ValueError):
IBMFloat.from_float(float('inf'))
def test_too_large(self):
with self.assertRaises(OverflowError):
IBMFloat.from_float(MAX_IBM_FLOAT * 10)
def test_too_small(self):
with self.assertRaises(OverflowError):
IBMFloat.from_float(MIN_IBM_FLOAT * 10)
@given(floats_in_range(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
def test_bool(self, f):
self.assertEqual(bool(IBMFloat.from_float(f)), bool(f))
@given(integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255))
def test_bytes_roundtrip(self, a, b, c, d):
b = byte_string((a, b, c, d))
ibm = IBMFloat.from_bytes(b)
self.assertEqual(bytes(ibm), b)
@given(floats_in_range(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
def test_floats_roundtrip(self, f):
ibm = IBMFloat.from_float(f)
self.assertTrue(almost_equal(f, float(ibm), epsilon=EPSILON_IBM_FLOAT))
@given(integers_in_range(0, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
floats_in_range(0.0, 1.0))
def test_trunc_above_zero(self, i, f):
assume(f != 1.0)
ieee = i + f
ibm = IBMFloat.from_float(ieee)
self.assertEqual(trunc(ibm), i)
@given(integers_in_range(MIN_EXACT_INTEGER_IBM_FLOAT + 1, 0),
floats_in_range(0.0, 1.0))
def test_trunc_below_zero(self, i, f):
assume(f != 1.0)
ieee = i - f
ibm = IBMFloat.from_float(ieee)
self.assertEqual(trunc(ibm), i)
@given(integers_in_range(MIN_EXACT_INTEGER_IBM_FLOAT, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
floats_in_range(0.0, 1.0))
def test_ceil(self, i, f):
assume(f != 1.0)
ieee = i + f
ibm = IBMFloat.from_float(ieee)
self.assertEqual(math.ceil(ibm), i + 1)
@given(integers_in_range(MIN_EXACT_INTEGER_IBM_FLOAT, MAX_EXACT_INTEGER_IBM_FLOAT - 1),
floats_in_range(0.0, 1.0))
def test_floor(self, i, f):
assume(f != 1.0)
ieee = i + f
ibm = IBMFloat.from_float(ieee)
self.assertEqual(math.floor(ibm), i)
def test_normalise_subnormal_expect_failure(self):
# This float has an base-16 exponent of -64 (the minimum) and cannot be normalised
ibm = IBMFloat.from_float(1.6472184286297693e-83)
assert ibm.is_subnormal()
with self.assertRaises(FloatingPointError):
ibm.normalize()
def test_normalise_subnormal1(self):
ibm = IBMFloat.from_bytes((0b01000000, 0b00000000, 0b11111111, 0b00000000))
assert ibm.is_subnormal()
normalized = ibm.normalize()
self.assertFalse(normalized.is_subnormal())
def test_normalise_subnormal2(self):
ibm = IBMFloat.from_bytes((64, 1, 0, 0))
assert ibm.is_subnormal()
normalized = ibm.normalize()
self.assertFalse(normalized.is_subnormal())
@given(integers_in_range(128, 255),
integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(4, 23))
def test_normalise_subnormal(self, b, c, d, shift):
mantissa = (b << 16) | (c << 8) | d
assume(mantissa != 0)
mantissa >>= shift
assert mantissa != 0
sa = EXPONENT_BIAS
sb = (mantissa >> 16) & 0xff
sc = (mantissa >> 8) & 0xff
sd = mantissa & 0xff
ibm = IBMFloat.from_bytes((sa, sb, sc, sd))
assert ibm.is_subnormal()
normalized = ibm.normalize()
self.assertFalse(normalized.is_subnormal())
@given(integers_in_range(128, 255),
integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(4, 23))
def test_zero_subnormal(self, b, c, d, shift):
mantissa = (b << 16) | (c << 8) | d
assume(mantissa != 0)
mantissa >>= shift
assert mantissa != 0
sa = EXPONENT_BIAS
sb = (mantissa >> 16) & 0xff
sc = (mantissa >> 8) & 0xff
sd = mantissa & 0xff
ibm = IBMFloat.from_bytes((sa, sb, sc, sd))
assert ibm.is_subnormal()
z = ibm.zero_subnormal()
self.assertTrue(z.is_zero())
@given(integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255))
def test_abs(self, a, b, c, d):
ibm = IBMFloat.from_bytes((a, b, c, d))
abs_ibm = abs(ibm)
self.assertGreaterEqual(abs_ibm.signbit, 0)
@given(integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255),
integers_in_range(0, 255))
def test_negate_non_zero(self, a, b, c, d):
ibm = IBMFloat.from_bytes((a, b, c, d))
assume(not ibm.is_zero())
negated = -ibm
self.assertNotEqual(ibm.signbit, negated.signbit)
def test_negate_zero(self):
zero = IBMFloat.from_float(0.0)
negated = -zero
self.assertTrue(negated.is_zero())
@given(floats_in_range(MIN_IBM_FLOAT, MAX_IBM_FLOAT))
def test_signbit(self, f):
ltz = f < 0
ibm = IBMFloat.from_float(f)
self.assertEqual(ltz, ibm.signbit)
@given(floats_in_range(-1.0, +1.0),
integers_in_range(-256, 255))
def test_ldexp_frexp(self, fraction, exponent):
try:
ibm = IBMFloat.ldexp(fraction, exponent)
except OverflowError:
assume(False)
else:
f, e = ibm.frexp()
self.assertTrue(almost_equal(fraction * 2**exponent, f * 2**e, epsilon=EPSILON_IBM_FLOAT))
@given(floats_in_range(MIN_IBM_FLOAT, MAX_IBM_FLOAT),
floats_in_range(0.0, 1.0))
def test_add(self, f, p):
a = f * p
b = f - a
ibm_a = IBMFloat.from_float(a)
ibm_b = IBMFloat.from_float(b)
ibm_c = ibm_a + ibm_b
ieee_a = float(ibm_a)
ieee_b = float(ibm_b)
ieee_c = ieee_a + ieee_b
self.assertTrue(almost_equal(ieee_c, ibm_c, epsilon=EPSILON_IBM_FLOAT * 4))
@given(floats_in_range(0, MAX_IBM_FLOAT),
floats_in_range(0, MAX_IBM_FLOAT))
def test_sub(self, a, b):
ibm_a = IBMFloat.from_float(a)
ibm_b = IBMFloat.from_float(b)
ibm_c = ibm_a - ibm_b
ieee_a = float(ibm_a)
ieee_b = float(ibm_b)
ieee_c = ieee_a - ieee_b
self.assertTrue(almost_equal(ieee_c, ibm_c, epsilon=EPSILON_IBM_FLOAT))
if __name__ == '__main__':
unittest.main()
+1 -1
View File
@@ -1,7 +1,7 @@
import unittest
from hypothesis import given, assume
from hypothesis.descriptors import integers_in_range
from hypothesis.specifiers import integers_in_range
from segpy.util import batched