Some progress on testing and implementing the IBMFloat.

This commit is contained in:
Robert Smallshire
2015-03-13 12:36:11 +01:00
parent 52e2967ec2
commit b4b0fe5d92
2 changed files with 129 additions and 26 deletions
+56 -23
View File
@@ -224,9 +224,29 @@ class IBMFloat(Real):
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):
return self._data[0] & 0x80
"""True if the value is negative, otherwise False."""
return bool(self._data[0] & 0x80)
def __float__(self):
return ibm2ieee(self._data)
@@ -244,25 +264,40 @@ class IBMFloat(Real):
return not self.is_zero()
def is_zero(self):
return all(b == 0 for b in self._data)
return self.int_mantissa == 0
def __nonzero__(self):
return not self.is_zero()
def is_subnormal(self):
return (not self.is_zero()) and (self._data[1] < 16)
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):
raise NotImplemented
# return the mantissa and exponent
"""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],
@@ -270,6 +305,9 @@ class IBMFloat(Real):
data[3]))
def __abs__(self):
if self.is_zero():
return IBM_FLOAT_ZERO
data = self._data
return IBMFloat((data[0] & 0b01111111,
data[1],
@@ -278,7 +316,9 @@ class IBMFloat(Real):
def __eq__(self, rhs):
if not isinstance(rhs, IBMFloat):
return NotImplemented
nlhs = self.normalize() if self.is_subnormal() else self
nrhs = rhs.normalize()
if
# TODO: Consider forcing normalisation
return self._data == rhs._data
@@ -322,10 +362,12 @@ class IBMFloat(Real):
return float(self) <= float(rhs)
def __ceil__(self):
return ceil(float(self))
t = trunc(self)
return t if self.signbit else t + 1
def __floor__(self):
return floor(float(self))
t = trunc(self)
return t - 1 if self.signbit else t
@property
def exp16(self):
@@ -344,26 +386,17 @@ class IBMFloat(Real):
exponent_16 = self.exp16
mantissa = self.int_mantissa
# 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
magnitude = truncated_mantissa * pow(16, exponent_16) >> MAX_BITS_PRECISION_IBM_FLOAT
return sign * magnitude
def normalize(self):
"""Attempt to normalize the floating point value.
"""Normalize the floating point value.
Returns:
A normalized IBMFloat equal in value to this object.
@@ -371,12 +404,12 @@ class IBMFloat(Real):
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
if mantissa == 0:
return IBM_FLOAT_ZERO
while mantissa < (1 << 20):
new_exponent_16 = exponent_16 - 1
if not (-64 <= new_exponent_16 < 64):
@@ -387,7 +420,7 @@ class IBMFloat(Real):
exponent_16_biased = exponent_16 + EXPONENT_BIAS
sign = self.signbit << 7
sign = int(self.signbit) << 7
a = sign | exponent_16_biased
b = (mantissa >> 16) & 0xff
@@ -412,7 +445,7 @@ class IBMFloat(Real):
return IBMFloat.from_float(p) if isinstance(rhs, IBMFloat) else p
def __int__(self):
raise trunc(self)
return trunc(self)
IBM_FLOAT_ZERO = IBMFloat.from_bytes(IBM_ZERO_BYTES)
+73 -3
View File
@@ -2,7 +2,8 @@ from math import trunc, floor
import unittest
from hypothesis import given, assume
from hypothesis.descriptors import integers_in_range, floats_in_range
from hypothesis.descriptors import integers_in_range, floats_in_range, just
import math
from segpy.portability import byte_string
from segpy.ibm_float import (ieee2ibm, ibm2ieee, MAX_IBM_FLOAT, SMALLEST_POSITIVE_NORMAL_IBM_FLOAT,
@@ -212,6 +213,22 @@ class TestIBMFloat(unittest.TestCase):
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))
@given(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))
@given(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)
@@ -251,17 +268,70 @@ class TestIBMFloat(unittest.TestCase):
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):
b = byte_string((a, b, c, d))
ibm = IBMFloat.from_bytes(b)
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))
if __name__ == '__main__':