Author: mattip <[email protected]>
Branch: 
Changeset: r80593:fd1672bd10fe
Date: 2015-11-08 19:57 +0200
http://bitbucket.org/pypy/pypy/changeset/fd1672bd10fe/

Log:    merge propogate-nans, which tries harder to preserve nan mantissa
        values across float conversions

diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -18,3 +18,10 @@
 .. branch: int_0/i-need-this-library-to-build-on-ubuntu-1-1446717626227
 
 Document that libgdbm-dev is required for translation/packaging
+
+.. branch: propogate-nans
+
+Ensure that ndarray conversion from int16->float16->float32->float16->int16
+preserves all int16 values, even across nan conversions. Also fix argmax, 
argmin
+for nan comparisons
+
diff --git a/pypy/module/micronumpy/loop.py b/pypy/module/micronumpy/loop.py
--- a/pypy/module/micronumpy/loop.py
+++ b/pypy/module/micronumpy/loop.py
@@ -534,10 +534,10 @@
             while not inner_iter.done(inner_state):
                 arg_driver.jit_merge_point(shapelen=shapelen, dtype=dtype)
                 w_val = inner_iter.getitem(inner_state)
-                new_best = getattr(dtype.itemtype, op_name)(cur_best, w_val)
-                if dtype.itemtype.ne(new_best, cur_best):
+                old_best = getattr(dtype.itemtype, op_name)(cur_best, w_val)
+                if not old_best:
                     result = idx
-                    cur_best = new_best
+                    cur_best = w_val
                 inner_state = inner_iter.next(inner_state)
                 idx += 1
             result = get_dtype_cache(space).w_longdtype.box(result)
@@ -557,17 +557,17 @@
         while not iter.done(state):
             arg_flat_driver.jit_merge_point(shapelen=shapelen, dtype=dtype)
             w_val = iter.getitem(state)
-            new_best = getattr(dtype.itemtype, op_name)(cur_best, w_val)
-            if dtype.itemtype.ne(new_best, cur_best):
+            old_best = getattr(dtype.itemtype, op_name)(cur_best, w_val)
+            if not old_best:
                 result = idx
-                cur_best = new_best
+                cur_best = w_val
             state = iter.next(state)
             idx += 1
         return result
 
     return argmin_argmax, argmin_argmax_flat
-argmin, argmin_flat = _new_argmin_argmax('min')
-argmax, argmax_flat = _new_argmin_argmax('max')
+argmin, argmin_flat = _new_argmin_argmax('argmin')
+argmax, argmax_flat = _new_argmin_argmax('argmax')
 
 dot_driver = jit.JitDriver(name = 'numpy_dot',
                            greens = ['dtype'],
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
@@ -1852,6 +1852,24 @@
         a = array([(1, 2)], dtype=[('a', 'int64'), ('b', 'int64')])
         assert a.view('S16')[0] == '\x01' + '\x00' * 7 + '\x02'
 
+    def test_half_conversions(self):
+        from numpy import array, arange
+        from math import isnan, isinf
+        e = array([0, -1, -float('inf'), float('nan'), 6], dtype='float16')
+        assert map(isnan, e) == [False, False, False, True, False]
+        assert map(isinf, e) == [False, False, True, False, False]
+        assert e.argmax() == 3
+        # numpy preserves value for uint16 -> cast_as_float16 -> 
+        #     convert_to_float64 -> convert_to_float16 -> uint16
+        #  even for float16 various float16 nans
+        all_f16 = arange(0xfe00, 0xffff, dtype='uint16')
+        all_f16.dtype = 'float16'
+        all_f32 = array(all_f16, dtype='float32')
+        b = array(all_f32, dtype='float16')
+        c = b.view(dtype='uint16')
+        d = all_f16.view(dtype='uint16')
+        assert (c == d).all()
+
     def test_ndarray_view_empty(self):
         from numpy import array, dtype
         x = array([], dtype=[('a', 'int8'), ('b', 'int8')])
diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py
--- a/pypy/module/micronumpy/types.py
+++ b/pypy/module/micronumpy/types.py
@@ -345,6 +345,14 @@
     def min(self, v1, v2):
         return min(v1, v2)
 
+    @raw_binary_op
+    def argmax(self, v1, v2):
+        return v1 >= v2
+
+    @raw_binary_op
+    def argmin(self, v1, v2):
+        return v1 <= v2
+
     @raw_unary_op
     def rint(self, v):
         float64 = Float64(self.space)
@@ -820,6 +828,14 @@
     def min(self, v1, v2):
         return v1 if v1 <= v2 or rfloat.isnan(v1) else v2
 
+    @raw_binary_op
+    def argmax(self, v1, v2):
+        return v1 >= v2 or rfloat.isnan(v1)
+
+    @raw_binary_op
+    def argmin(self, v1, v2):
+        return v1 <= v2 or rfloat.isnan(v1)
+
     @simple_binary_op
     def fmax(self, v1, v2):
         return v1 if v1 >= v2 or rfloat.isnan(v2) else v2
@@ -1407,6 +1423,16 @@
             return v1
         return v2
 
+    def argmin(self, v1, v2):
+        if self.le(v1, v2) or self.isnan(v1):
+            return True
+        return False
+
+    def argmax(self, v1, v2):
+        if self.ge(v1, v2) or self.isnan(v1):
+            return True
+        return False
+
     @complex_binary_op
     def floordiv(self, v1, v2):
         (r1, i1), (r2, i2) = v1, v2
@@ -1927,6 +1953,18 @@
             return v1
         return v2
 
+    @raw_binary_op
+    def argmax(self, v1, v2):
+        if self.space.is_true(self.space.ge(v1, v2)):
+            return True
+        return False
+
+    @raw_binary_op
+    def argmin(self, v1, v2):
+        if self.space.is_true(self.space.le(v1, v2)):
+            return True
+        return False
+
     @raw_unary_op
     def bool(self,v):
         return self._obool(v)
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,7 +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 r_ulonglong, r_longlong, 
LONGLONG, ULONGLONG, cast
+from rpython.rlib.longlong2float import longlong2float, float2longlong
 
 def round_to_nearest(x):
     """Python 3 style round:  round a float x to the nearest int, but
@@ -60,7 +61,16 @@
 
     if exp == MAX_EXP - MIN_EXP + 2:
         # nan or infinity
-        result = rfloat.NAN if mant else rfloat.INFINITY
+        if mant == 0:
+            result = rfloat.INFINITY
+        else:
+            # preserve at most 52 bits of mant value, but pad w/zeros
+            exp = r_ulonglong(0x7ff) << 52
+            sign = r_ulonglong(sign) << 63
+            mant = r_ulonglong(mant) << (52 - MANT_DIG) 
+            uint = exp | mant | sign
+            result =  longlong2float(cast(LONGLONG, uint))
+            return result
     elif exp == 0:
         # subnormal or zero
         result = math.ldexp(mant, MIN_EXP - MANT_DIG)
@@ -72,7 +82,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
@@ -100,7 +110,13 @@
 
     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 = r_ulonglong(0x7ff) << 52
+            uint = exp | r_ulonglong(mant) | r_ulonglong(sign)
+            result =  longlong2float(cast(LONGLONG, uint))
+            return result
     else:
         # normal
         result = math.ldexp(mant, exp + MIN_EXP - MANT_DIG - 1)
@@ -128,13 +144,19 @@
         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):
+        asint = cast(ULONGLONG, float2longlong(x))
+        mant = asint & ((r_ulonglong(1) << 51) - 1)
+        sign = asint >> 63
+        # shift off lower bits, perhaps losing data
+        if MANT_DIG <= 52:
+            mant = mant >> (52 - MANT_DIG)
+        if mant == 0:
+            mant = r_ulonglong(1) << (MANT_DIG - 2) - 1
+        exp = MAX_EXP - MIN_EXP + 2
     elif x == 0.0:
         mant = r_ulonglong(0)
         exp = 0
@@ -167,7 +189,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)
@@ -187,13 +209,16 @@
         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):
+        asint = cast(ULONGLONG, float2longlong(x))
+        mant = asint & ((r_ulonglong(1) << 51) - 1)
+        if mant == 0:
+            mant = r_ulonglong(1) << (MANT_DIG-2) - 1
+        sign = asint < 0
+        exp = MAX_EXP - MIN_EXP + 2
     elif x == 0.0:
         mant = r_ulonglong(0)
         exp = 0
@@ -221,12 +246,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):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to