mirror of
https://github.com/wassname/segpy.git
synced 2026-06-27 19:00:53 +08:00
Some progress on testing and implementing the IBMFloat.
This commit is contained in:
+56
-23
@@ -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
@@ -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__':
|
||||
|
||||
Reference in New Issue
Block a user