Commits

Brian Kearns committed 0a025c4

clean up rstruct.ieee 80-bit float packing and improve tests

  • Participants
  • Parent commits 35075dd

Comments (0)

Files changed (4)

File pypy/module/micronumpy/test/test_numarray.py

         assert a[1] == 0xff
         assert len(a.data) == 16
 
-
     def test_explicit_dtype_conversion(self):
         from _numpypy import array
         a = array([1.0, 2.0])
         b = array(a, dtype='d')
         assert a.dtype is b.dtype
 
+
 class AppTestMultiDim(BaseNumpyAppTest):
     def test_init(self):
         import _numpypy

File pypy/module/micronumpy/types.py

 
         def byteswap(self, w_v):
             value = self.unbox(w_v)
-            result = StringBuilder(12)
-            pack_float80(result, value, not native_is_bigendian)
+            result = StringBuilder(10)
+            pack_float80(result, value, 10, not native_is_bigendian)
             return self.box(unpack_float80(result.build(), native_is_bigendian))
 
     NonNativeFloat96 = Float96
 
         def byteswap(self, w_v):
             value = self.unbox(w_v)
-            result = StringBuilder(16)
-            pack_float80(result, value, not native_is_bigendian)
+            result = StringBuilder(10)
+            pack_float80(result, value, 10, not native_is_bigendian)
             return self.box(unpack_float80(result.build(), native_is_bigendian))
 
     NonNativeFloat128 = Float128

File rpython/rlib/rstruct/ieee.py

     return int_part
 
 def float_unpack(Q, size):
-    """Convert a 16-bit, 32-bit 64-bit integer created
+    """Convert a 16-bit, 32-bit, or 64-bit integer created
     by float_pack into a Python float."""
     if size == 8:
         MIN_EXP = -1021  # = sys.float_info.min_exp
         MAX_EXP = 1024   # = sys.float_info.max_exp
         MANT_DIG = 53    # = sys.float_info.mant_dig
         BITS = 64
-        one = r_ulonglong(1)
     elif size == 4:
         MIN_EXP = -125   # C's FLT_MIN_EXP
         MAX_EXP = 128    # FLT_MAX_EXP
         MANT_DIG = 24    # FLT_MANT_DIG
         BITS = 32
-        one = r_ulonglong(1)
     elif size == 2:
         MIN_EXP = -13
         MAX_EXP = 16
         MANT_DIG = 11
         BITS = 16
-        one = r_ulonglong(1)
     else:
         raise ValueError("invalid size value")
 
             raise ValueError("input '%r' out of range '%r'" % (Q, Q>>BITS))
 
     # extract pieces with assumed 1.mant values
+    one = r_ulonglong(1)
     sign = rarithmetic.intmask(Q >> BITS - 1)
     exp = rarithmetic.intmask((Q & ((one << BITS - 1) - (one << MANT_DIG - 1))) >> MANT_DIG - 1)
     mant = Q & ((one << MANT_DIG - 1) - 1)
         result = math.ldexp(mant, exp + MIN_EXP - MANT_DIG - 1)
     return -result if sign else result
 
-def float_unpack80(QQ):
+def float_unpack80(QQ, size):
     '''Unpack a (mant, exp) tuple of r_ulonglong in 80-bit extended format
     into a long double float
     '''
-    MIN_EXP = -16381
-    MAX_EXP = 16384
-    MANT_DIG = 64
-    TOPBITS = 80 - 64
-    one = r_ulonglong(1)
+    if size == 10 or size == 12 or size == 16:
+        MIN_EXP = -16381
+        MAX_EXP = 16384
+        MANT_DIG = 64
+        TOP_BITS = 80 - 64
+    else:
+        raise ValueError("invalid size value")
+
     if len(QQ) != 2:
         raise ValueError("QQ must be two 64 bit uints")
+
     if not objectmodel.we_are_translated():
         # This tests generates wrong code when translated:
         # with gcc, shifting a 64bit int by 64 bits does
         # not change the value.
-        if QQ[1] >> TOPBITS:
-            raise ValueError("input '%r' out of range '%r'" % (QQ, QQ[1]>>TOPBITS))
+        if QQ[1] >> TOP_BITS:
+            raise ValueError("input '%r' out of range '%r'" % (QQ, QQ[1]>>TOP_BITS))
 
     # extract pieces with explicit one in MANT_DIG
-    sign = rarithmetic.intmask(QQ[1] >> TOPBITS - 1)
-    exp = rarithmetic.intmask((QQ[1] & ((one << TOPBITS - 1) - 1)))
+    one = r_ulonglong(1)
+    sign = rarithmetic.intmask(QQ[1] >> TOP_BITS - 1)
+    exp = rarithmetic.intmask((QQ[1] & ((one << TOP_BITS - 1) - 1)))
     mant = QQ[0]
 
     if exp == MAX_EXP - MIN_EXP + 2:
     sign = r_ulonglong(sign)
     return ((sign << BITS - 1) | (exp << MANT_DIG - 1)) | mant
 
-def float_pack80(x):
+def float_pack80(x, size):
     """Convert a Python float or longfloat x into two 64-bit unsigned integers
     with 80 bit extended representation."""
-    MIN_EXP = -16381
-    MAX_EXP = 16384
-    MANT_DIG = 64
-    BITS = 80
     x = float(x) # longfloat not really supported
+    if size == 10 or size == 12 or size == 16:
+        MIN_EXP = -16381
+        MAX_EXP = 16384
+        MANT_DIG = 64
+        BITS = 80
+    else:
+        raise ValueError("invalid size value")
+
     sign = rfloat.copysign(1.0, x) < 0.0
     if not rfloat.isfinite(x):
         if rfloat.isinf(x):
     result.append("".join(l))
 
 @jit.unroll_safe
-def pack_float80(result, x, be):
+def pack_float80(result, x, size, be):
     l = []
-    unsigned = float_pack80(x)
+    unsigned = float_pack80(x, size)
     for i in range(8):
         l.append(chr((unsigned[0] >> (i * 8)) & 0xFF))
     for i in range(2):
         l.append(chr((unsigned[1] >> (i * 8)) & 0xFF))
+    for i in range(size - 10):
+        l.append('\x00')
     if be:
         l.reverse()
     result.append("".join(l))
 
 def unpack_float(s, be):
     unsigned = r_ulonglong(0)
-    for i in range(len(s)):
-        c = ord(s[len(s) - 1 - i if be else i])
+    for i in range(min(len(s), 8)):
+        c = ord(s[-i - 1 if be else i])
         unsigned |= r_ulonglong(c) << (i * 8)
     return float_unpack(unsigned, len(s))
 
 def unpack_float80(s, be):
-    if len(s) != 10:
-        raise ValueError
     QQ = [r_ulonglong(0), r_ulonglong(0)]
     for i in range(8):
-        c = ord(s[9 - i if be else i])
+        c = ord(s[-i - 1 if be else i])
         QQ[0] |= r_ulonglong(c) << (i * 8)
     for i in range(8, 10):
-        c = ord(s[9 - i if be else i])
+        c = ord(s[-i - 1 if be else i])
         QQ[1] |= r_ulonglong(c) << ((i - 8) * 8)
-    return float_unpack80(QQ)
+    return float_unpack80(QQ, len(s))

File rpython/rlib/rstruct/test/test_ieee.py

 from rpython.translator.c.test.test_genc import compile
 
 
+class TestFloatSpecific:
+    def test_halffloat_exact(self):
+        #testcases generated from numpy.float16(x).view('uint16')
+        cases = [[0, 0], [10, 18688], [-10, 51456], [10e3, 28898],
+                 [float('inf'), 31744], [-float('inf'), 64512]]
+        for c, h in cases:
+            hbit = ieee.float_pack(c, 2)
+            assert hbit == h
+            assert c == ieee.float_unpack(h, 2)
+
+    def test_halffloat_inexact(self):
+        #testcases generated from numpy.float16(x).view('uint16')
+        cases = [[10.001, 18688, 10.], [-10.001, 51456, -10],
+                 [0.027588, 10000, 0.027587890625],
+                 [22001, 30047, 22000]]
+        for c, h, f in cases:
+            hbit = ieee.float_pack(c, 2)
+            assert hbit == h
+            assert f == ieee.float_unpack(h, 2)
+
+    def test_halffloat_overunderflow(self):
+        import math
+        cases = [[670000, float('inf')], [-67000, -float('inf')],
+                 [1e-08, 0], [-1e-8, -0.]]
+        for f1, f2 in cases:
+            try:
+                f_out = ieee.float_unpack(ieee.float_pack(f1, 2), 2)
+            except OverflowError:
+                f_out = math.copysign(float('inf'), f1)
+            assert f_out == f2
+            assert math.copysign(1., f_out) == math.copysign(1., f2)
+
+    def test_float80_exact(self):
+        s = []
+        ieee.pack_float80(s, -1., 16, False)
+        assert repr(s[-1]) == repr('\x00\x00\x00\x00\x00\x00\x00\x80\xff\xbf\x00\x00\x00\x00\x00\x00')
+        ieee.pack_float80(s, -1., 16, True)
+        assert repr(s[-1]) == repr('\x00\x00\x00\x00\x00\x00\xbf\xff\x80\x00\x00\x00\x00\x00\x00\x00')
+        ieee.pack_float80(s, -123.456, 16, False)
+        assert repr(s[-1]) == repr('\x00\xb8\xf3\xfd\xd4x\xe9\xf6\x05\xc0\x00\x00\x00\x00\x00\x00')
+        ieee.pack_float80(s, -123.456, 16, True)
+        assert repr(s[-1]) == repr('\x00\x00\x00\x00\x00\x00\xc0\x05\xf6\xe9x\xd4\xfd\xf3\xb8\x00')
+
+        x = ieee.unpack_float80('\x00\x00\x00\x00\x00\x00\x00\x80\xff?\xc8\x01\x00\x00\x00\x00', False)
+        assert x == 1.0
+        x = ieee.unpack_float80('\x00\x00\x7f\x83\xe1\x91?\xff\x80\x00\x00\x00\x00\x00\x00\x00', True)
+        assert x == 1.0
+
+
 class TestFloatPacking:
     def setup_class(cls):
         if sys.version_info < (2, 6):
 
     def check_float(self, x):
         # check roundtrip
-        Q = ieee.float_pack(x, 8)
-        y = ieee.float_unpack(Q, 8)
-        assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
+        for size in [10, 12, 16]:
+            for be in [False, True]:
+                Q = []
+                ieee.pack_float80(Q, x, size, be)
+                Q = Q[0]
+                y = ieee.unpack_float80(Q, be)
+                assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
 
-        Q = ieee.float_pack80(x)
-        y = ieee.float_unpack80(Q)
-        assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
-
-        Q = []
-        ieee.pack_float(Q, x, 8, False)
-        Q = Q[0]
-        y = ieee.unpack_float(Q, False)
-        assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
-
-        Q = []
-        ieee.pack_float80(Q, x, False)
-        Q = Q[0]
-        y = ieee.unpack_float80(Q, False)
-        assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
+        for be in [False, True]:
+            Q = []
+            ieee.pack_float(Q, x, 8, be)
+            Q = Q[0]
+            y = ieee.unpack_float(Q, be)
+            assert repr(x) == repr(y), '%r != %r, Q=%r' % (x, y, Q)
 
         # check that packing agrees with the struct module
         struct_pack8 = struct.unpack('<Q', struct.pack('<d', x))[0]
         self.check_float(0.0)
         self.check_float(-0.0)
 
-    def test_check_size(self):
-        # these were refactored into separate pack80/unpack80 functions
-        py.test.raises(ValueError, ieee.float_pack, 1.0, 12)
-        py.test.raises(ValueError, ieee.float_pack, 1.0, 16)
-        py.test.raises(ValueError, ieee.float_unpack, 1, 12)
-        py.test.raises(ValueError, ieee.float_unpack, 1, 16)
-
     def test_nans(self):
-        Q = ieee.float_pack80(float('nan'))
-        y = ieee.float_unpack80(Q)
-        assert repr(y) == 'nan'
-        Q = ieee.float_pack(float('nan'), 8)
-        y = ieee.float_unpack(Q, 8)
-        assert repr(y) == 'nan'
-        L = ieee.float_pack(float('nan'), 4)
-        z = ieee.float_unpack(L, 4)
-        assert repr(z) == 'nan'
-        L = ieee.float_pack(float('nan'), 2)
-        z = ieee.float_unpack(L, 2)
-        assert repr(z) == 'nan'
+        self.check_float(float('nan'))
 
     def test_simple(self):
         test_values = [1e-10, 0.00123, 0.5, 0.7, 1.0, 123.456, 1e10]
 
     def test_random(self):
         # construct a Python float from random integer, using struct
-        for _ in xrange(100000):
+        for _ in xrange(10000):
             Q = random.randrange(2**64)
             x = struct.unpack('<d', struct.pack('<Q', Q))[0]
             # nans are tricky:  we can't hope to reproduce the bit
                 continue
             self.check_float(x)
 
-    def test_halffloat_exact(self):
-        #testcases generated from numpy.float16(x).view('uint16')
-        cases = [[0, 0], [10, 18688], [-10, 51456], [10e3, 28898],
-                 [float('inf'), 31744], [-float('inf'), 64512]]
-        for c, h in cases:
-            hbit = ieee.float_pack(c, 2)
-            assert hbit == h
-            assert c == ieee.float_unpack(h, 2)
-
-    def test_halffloat_inexact(self):
-        #testcases generated from numpy.float16(x).view('uint16')
-        cases = [[10.001, 18688, 10.], [-10.001, 51456, -10],
-                 [0.027588, 10000, 0.027587890625],
-                 [22001, 30047, 22000]]
-        for c, h, f in cases:
-            hbit = ieee.float_pack(c, 2)
-            assert hbit == h
-            assert f == ieee.float_unpack(h, 2)
-
-    def test_halffloat_overunderflow(self):
-        import math
-        cases = [[670000, float('inf')], [-67000, -float('inf')],
-                 [1e-08, 0], [-1e-8, -0.]]
-        for f1, f2 in cases:
-            try:
-                f_out = ieee.float_unpack(ieee.float_pack(f1, 2), 2)
-            except OverflowError:
-                f_out = math.copysign(float('inf'), f1)
-            assert f_out == f2
-            assert math.copysign(1., f_out) == math.copysign(1., f2)
-
 
 class TestCompiled:
     def test_pack_float(self):