mirror of
https://github.com/wassname/segpy.git
synced 2026-08-11 11:25:46 +08:00
Adds a new IBMFloat type which losslessly stores IBM 32 bit floats
This commit is contained in:
+68
-4
@@ -7,9 +7,11 @@ 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))
|
||||
IBM_FLOAT32_MAX_BITS_PRECISION = 24
|
||||
IBM_FLOAT32_MIN_BITS_PRECISION = 21 # The first 3 bits of the mantissa may be zero
|
||||
IBM_FLOAT32_EPSILON = pow(2.0, -(IBM_FLOAT32_MIN_BITS_PRECISION - 1))
|
||||
_L24 = long_int(2) ** IBM_FLOAT32_MAX_BITS_PRECISION
|
||||
_F24 = float(pow(2, IBM_FLOAT32_MAX_BITS_PRECISION))
|
||||
|
||||
|
||||
def ibm2ieee(big_endian_bytes):
|
||||
@@ -35,7 +37,7 @@ def ibm2ieee(big_endian_bytes):
|
||||
|
||||
|
||||
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 +114,65 @@ def ieee2ibm(f):
|
||||
d = mantissa & 0xff
|
||||
|
||||
return byte_string((a, b, c, d))
|
||||
|
||||
|
||||
class IBMFloat(object):
|
||||
__slots__ = ['_data']
|
||||
|
||||
def __init__(self, b):
|
||||
"""Initialise IBMFloat from an IEEE float.
|
||||
|
||||
Args:
|
||||
b: A byte sequence containing exactly four bytes
|
||||
|
||||
Raises:
|
||||
ValueError: If b does not contain exactly four values in the range 0-255.
|
||||
"""
|
||||
data = bytes(b)
|
||||
num_bytes = len(data)
|
||||
if num_bytes != 4:
|
||||
raise ValueError("{} cannot be constructed from {} values".format(self.__class__.__name__, num_bytes))
|
||||
self._data = data
|
||||
|
||||
@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_bytes(cls, b):
|
||||
return cls(b)
|
||||
|
||||
def __float__(self):
|
||||
return ibm2ieee(self._data)
|
||||
|
||||
def __bytes__(self):
|
||||
return self._data
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({!r})".format(self.__class__.__name__, self._data)
|
||||
|
||||
def __bool__(self):
|
||||
return not self.is_zero()
|
||||
|
||||
def is_zero(self):
|
||||
return all(b == 0 for b in self._data)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not self.is_zero()
|
||||
|
||||
def is_subnormal(self):
|
||||
return (not self.is_zero()) and (self._data[1] < 32)
|
||||
|
||||
|
||||
+8
-1
@@ -230,4 +230,11 @@ def round_up(integer, multiple):
|
||||
|
||||
def underscores_to_camelcase(s):
|
||||
"""Convert text_in_this_style to TextInThisStyle."""
|
||||
return ''.join(w.capitalize() for w in s.split('_'))
|
||||
return ''.join(w.capitalize() for w in s.split('_'))
|
||||
|
||||
|
||||
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
|
||||
+62
-1
@@ -1,8 +1,12 @@
|
||||
import unittest
|
||||
|
||||
from hypothesis import given
|
||||
from hypothesis.descriptors 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, IBM_FLOAT32_EPSILON)
|
||||
from segpy.util import almost_equal
|
||||
|
||||
|
||||
class Ibm2Ieee(unittest.TestCase):
|
||||
@@ -134,5 +138,62 @@ 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(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=IBM_FLOAT32_EPSILON))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user