[pypy-commit] pypy propogate-nans: do bit manipulation to preserve, where possible, mantissa values in nan floating point conversions

mattip noreply at buildbot.pypy.org
Fri Nov 6 06:29:13 EST 2015


Author: mattip <matti.picus at gmail.com>
Branch: propogate-nans
Changeset: r80563:f9a88e6a3a0f
Date: 2015-11-06 13:20 +0200
http://bitbucket.org/pypy/pypy/changeset/f9a88e6a3a0f/

Log:	do bit manipulation to preserve, where possible, mantissa values in
	nan floating point conversions

diff --git a/pypy/module/micronumpy/test/test_ndarray.py b/pypy/module/micronumpy/test/test_ndarray.py
--- a/pypy/module/micronumpy/test/test_ndarray.py
+++ b/pypy/module/micronumpy/test/test_ndarray.py
@@ -1853,14 +1853,14 @@
         assert a.view('S16')[0] == '\x01' + '\x00' * 7 + '\x02'
 
     def test_half_conversions(self):
+        # numpy preserves value for uint16 -> cast_as_float16 -> 
+        #     convert_to_float64 -> convert_to_float16 -> uint16
+        #  even for float16 various float16 nans
         from numpy import array, arange
-        all_f16 = arange(0xff10, 0xff20, dtype='uint16')
+        all_f16 = arange(0xfe00, 0xfe08, dtype='uint16')
         all_f16.dtype = 'float16'
-        print all_f16.view(dtype='uint16')
         all_f32 = array(all_f16, dtype='float32')
-        print all_f32.view(dtype='uint32')
         b = array(all_f32, dtype='float16')
-        print b.view(dtype='uint16')
         c = b.view(dtype='uint16')
         d = all_f16.view(dtype='uint16')
         assert (c == d).all()
diff --git a/rpython/rlib/rstruct/ieee.py b/rpython/rlib/rstruct/ieee.py
--- a/rpython/rlib/rstruct/ieee.py
+++ b/rpython/rlib/rstruct/ieee.py
@@ -5,8 +5,8 @@
 import math
 
 from rpython.rlib import rarithmetic, rfloat, objectmodel, jit
-from rpython.rlib.rarithmetic import r_ulonglong
-from rpython.rtyper.lltypesystem.rffi import DOUBLE, cast
+from rpython.rtyper.lltypesystem.rffi import r_ulonglong, ULONGP, DOUBLEP, cast
+from rpython.rtyper.lltypesystem import lltype
 
 def round_to_nearest(x):
     """Python 3 style round:  round a float x to the nearest int, but
@@ -64,7 +64,16 @@
         if mant == 0:
             result = rfloat.INFINITY
         else:
-            result = rfloat.NAN #cast(DOUBLE, mant | 
+            # preserve mant value but pad w/zeros
+            exp = 0x7ff << 52
+            sign = sign << 63
+            mant = mant << (53 - MANT_DIG)
+            b = lltype.malloc(ULONGP.TO, 1, flavor='raw')
+            b[0] = r_ulonglong(exp) | r_ulonglong(mant) | r_ulonglong(sign)
+            #b = cast(ULONGP, r_ulonglong(exp | mant | sign))
+            result = cast(DOUBLEP, b)[0]
+            lltype.free(b, flavor='raw')
+            return result
     elif exp == 0:
         # subnormal or zero
         result = math.ldexp(mant, MIN_EXP - MANT_DIG)
@@ -76,7 +85,7 @@
 
 def float_unpack80(QQ, size):
     '''Unpack a (mant, exp) tuple of r_ulonglong in 80-bit extended format
-    into a long double float
+    into a python float (a double)
     '''
     if size == 10 or size == 12 or size == 16:
         MIN_EXP = -16381
@@ -104,7 +113,16 @@
 
     if exp == MAX_EXP - MIN_EXP + 2:
         # nan or infinity
-        result = rfloat.NAN if mant &((one << MANT_DIG - 1) - 1) else rfloat.INFINITY
+        if mant == 0:
+            result = rfloat.INFINITY
+        else:
+            exp = 0x7ff << 52
+            b = lltype.malloc(ULONGP.TO, 1, flavor='raw')
+            sign = sign << 63
+            b[0] = r_ulonglong(exp) | r_ulonglong(mant) | r_ulonglong(sign)
+            result = cast(DOUBLEP, b)[0]
+            lltype.free(b, flavor='raw')
+            return result
     else:
         # normal
         result = math.ldexp(mant, exp + MIN_EXP - MANT_DIG - 1)
@@ -132,13 +150,21 @@
         raise ValueError("invalid size value")
 
     sign = rfloat.copysign(1.0, x) < 0.0
-    if not rfloat.isfinite(x):
-        if rfloat.isinf(x):
-            mant = r_ulonglong(0)
-            exp = MAX_EXP - MIN_EXP + 2
-        else:  # rfloat.isnan(x):
-            mant = r_ulonglong(1) << (MANT_DIG-2) # other values possible
-            exp = MAX_EXP - MIN_EXP + 2
+    if rfloat.isinf(x):
+        mant = r_ulonglong(0)
+        exp = MAX_EXP - MIN_EXP + 2
+    elif rfloat.isnan(x):
+        b = lltype.malloc(DOUBLEP.TO, 1, flavor='raw')
+        b[0] = x
+        asint = cast(ULONGP, b)[0]
+        mant = asint & ((r_ulonglong(1) << 52) - 1)
+        sign = asint >> 63
+        lltype.free(b, flavor='raw')
+        # shift off lower bits, perhaps losing data
+        mant = mant >> (53 - MANT_DIG)
+        if mant == 0:
+            mant = r_ulonglong(1) << (MANT_DIG-2)
+        exp = MAX_EXP - MIN_EXP + 2
     elif x == 0.0:
         mant = r_ulonglong(0)
         exp = 0
@@ -171,7 +197,7 @@
 
     # check constraints
     if not objectmodel.we_are_translated():
-        assert 0 <= mant < 1 << MANT_DIG - 1
+        assert 0 <= mant <= (1 << MANT_DIG) - 1
         assert 0 <= exp <= MAX_EXP - MIN_EXP + 2
         assert 0 <= sign <= 1
     exp = r_ulonglong(exp)
@@ -191,13 +217,17 @@
         raise ValueError("invalid size value")
 
     sign = rfloat.copysign(1.0, x) < 0.0
-    if not rfloat.isfinite(x):
-        if rfloat.isinf(x):
-            mant = r_ulonglong(0)
-            exp = MAX_EXP - MIN_EXP + 2
-        else:  # rfloat.isnan(x):
-            mant = (r_ulonglong(1) << (MANT_DIG-2)) - 1 # other values possible
-            exp = MAX_EXP - MIN_EXP + 2
+    if rfloat.isinf(x):
+        mant = r_ulonglong(0)
+        exp = MAX_EXP - MIN_EXP + 2
+    elif rfloat.isnan(x):  # rfloat.isnan(x):
+        b = lltype.malloc(DOUBLEP.TO, 1, flavor='raw')
+        b[0] = x
+        asint = cast(ULONGP, b)[0]
+        mant = asint & ((r_ulonglong(1) << 52) - 1)
+        lltype.free(b, flavor='raw')
+        sign = asint >> 63
+        exp = MAX_EXP - MIN_EXP + 2
     elif x == 0.0:
         mant = r_ulonglong(0)
         exp = 0
@@ -225,12 +255,12 @@
         if exp >= MAX_EXP - MIN_EXP + 2:
             raise OverflowError("float too large to pack in this format")
 
+        mant = mant << 1
     # check constraints
     if not objectmodel.we_are_translated():
-        assert 0 <= mant < 1 << MANT_DIG - 1
+        assert 0 <= mant <= (1 << MANT_DIG) - 1
         assert 0 <= exp <= MAX_EXP - MIN_EXP + 2
         assert 0 <= sign <= 1
-    mant = mant << 1
     exp = r_ulonglong(exp)
     sign = r_ulonglong(sign)
     return (mant, (sign << BITS - MANT_DIG - 1) | exp)
diff --git a/rpython/rlib/rstruct/test/test_ieee.py b/rpython/rlib/rstruct/test/test_ieee.py
--- a/rpython/rlib/rstruct/test/test_ieee.py
+++ b/rpython/rlib/rstruct/test/test_ieee.py
@@ -168,15 +168,31 @@
 
     def test_random(self):
         # construct a Python float from random integer, using struct
+        mantissa_mask = (1 << 53) - 1
         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
-            # pattern exactly, so check_float will fail for a random nan.
-            if isnan(x):
+            # pattern exactly, so check_float will fail for a nan
+            # whose mantissa does not fit into float16's mantissa.
+            if isnan(x) and (Q & mantissa_mask) >=  1 << 11:
                 continue
             self.check_float(x)
 
+    def test_various_nans(self):
+        # check patterns that should preserve the mantissa across nan conversions
+        maxmant64 = (1 << 52) - 1 # maximum double mantissa
+        maxmant16 = (1 << 10) - 1 # maximum float16 mantissa
+        assert maxmant64 >> 42 == maxmant16
+        exp = 0xfff << 52
+        for i in range(20):
+            val_to_preserve = exp | ((maxmant16 - i) << 42)
+            a = ieee.float_unpack(val_to_preserve, 8)
+            assert isnan(a), 'i %d, maxmant %s' % (i, hex(val_to_preserve))
+            b = ieee.float_pack(a, 8)
+            assert b == val_to_preserve, 'i %d, val %s b %s' % (i, hex(val_to_preserve), hex(b)) 
+            b = ieee.float_pack(a, 2)
+            assert b == 0xffff - i, 'i %d, b%s' % (i, hex(b))
 
 class TestCompiled:
     def test_pack_float(self):


More information about the pypy-commit mailing list