Repository: numba/numba

Issue:
UFunc error handling may differ `0 != -0`
See https://github.com/numba/numba/pull/1130/files#diff-228207ff3c5703f16e69104cecc65257R1323



Discussion:
Is this still an error? If so, can you post the symptoms more precisely?

Our ufunc may produce zero with the "wrong" sign when error occurs (e.g. divide-by-zero).  Remove the highlighted if-block in https://github.com/numba/numba/pull/1130/files#diff-228207ff3c5703f16e69104cecc65257R1323 in `_is_acceptable_error` to see the actual error.  


Resolving patch:
diff --git a/numba/targets/npyfuncs.py b/numba/targets/npyfuncs.py
--- a/numba/targets/npyfuncs.py
+++ b/numba/targets/npyfuncs.py
@@ -107,15 +107,6 @@ def _dispatch_func_by_name_type(context, builder, sig, args, table, user_name):
 
 
 
-def np_dummy_return_arg(context, builder, sig, args):
-    # sometimes a loop does nothing other than returning the first arg...
-    # for example, conjugate for non-complex numbers
-    # this function implements this.
-    _check_arity_and_homogeneity(sig, args, 1)
-    return args[0] # nothing to do...
-
-
-
 ########################################################################
 # Division kernels inspired by NumPy loops.c.src code
 #
@@ -584,21 +575,6 @@ def np_complex_rint_impl(context, builder, sig, args):
     return out._getvalue()
 
 
-########################################################################
-# NumPy conj/conjugate
-def np_complex_conjugate_impl(context, builder, sig, args):
-    _check_arity_and_homogeneity(sig, args, 1)
-    ty = sig.args[0]
-    float_ty = ty.underlying_float
-    complex_class = context.make_complex(ty)
-    in1 = complex_class(context, builder, value=args[0])
-    out = complex_class(context, builder)
-    ZERO = context.get_constant(float_ty, 0.0)
-    out.real = in1.real
-    out.imag = builder.fsub(ZERO, in1.imag)
-    return out._getvalue()
-
-
 ########################################################################
 # NumPy exp
 
@@ -1139,35 +1115,6 @@ def np_real_acos_impl(context, builder, sig, args):
                                        dispatch_table, 'arccos')
 
 
-def np_complex_acos_impl(context, builder, sig, args):
-    # npymath does not provide a complex acos. The code in funcs.inc.src
-    # is translated here...
-    # - j * log(x + j * sqrt(1 - sqr(x)))
-    _check_arity_and_homogeneity(sig, args, 1)
-
-    ty = sig.args[0]
-    binary_sig = typing.signature(*[ty]*3)
-
-    ONE = context.get_constant_generic(builder, ty, 1.0 + 0.0j)
-    ZERO = context.get_constant_generic(builder, ty, 0.0 + 0.0j)
-    I = context.get_constant_generic(builder, ty, 0.0 + 1.0j)
-
-    xx = np_complex_square_impl(context, builder, sig, args)
-    one_minus_xx = builtins.complex_sub_impl(context, builder, binary_sig,
-                                             [ONE, xx])
-    sqrt_res = np_complex_sqrt_impl(context, builder, sig, [one_minus_xx])
-    sqrt_res_j = builtins.complex_mul_impl(context, builder, binary_sig,
-                                           [I, sqrt_res])
-
-    log_in = builtins.complex_add_impl(context, builder, binary_sig,
-                                       [args[0], sqrt_res_j])
-    log_out = np_complex_log_impl(context, builder, sig, [log_in])
-    log_j = builtins.complex_mul_impl(context, builder, binary_sig,
-                                      [I, log_out])
-    return builtins.complex_sub_impl(context, builder, binary_sig,
-                                     [ZERO, log_j])
-
-
 ########################################################################
 # NumPy atan
 
diff --git a/numba/targets/ufunc_db.py b/numba/targets/ufunc_db.py
--- a/numba/targets/ufunc_db.py
+++ b/numba/targets/ufunc_db.py
@@ -47,7 +47,7 @@ def _fill_ufunc_db(ufunc_db):
     # some of these imports would cause a problem of circular
     # imports if done at global scope when importing the numba
     # module.
-    from . import builtins, npyfuncs
+    from . import builtins, npyfuncs, cmathimpl
 
     ufunc_db[np.negative] = {
         '?->?': builtins.bool_invert_impl,
@@ -273,20 +273,20 @@ def _fill_ufunc_db(ufunc_db):
     }
 
     ufunc_db[np.conjugate] = {
-        'b->b': npyfuncs.np_dummy_return_arg,
-        'B->B': npyfuncs.np_dummy_return_arg,
-        'h->h': npyfuncs.np_dummy_return_arg,
-        'H->H': npyfuncs.np_dummy_return_arg,
-        'i->i': npyfuncs.np_dummy_return_arg,
-        'I->I': npyfuncs.np_dummy_return_arg,
-        'l->l': npyfuncs.np_dummy_return_arg,
-        'L->L': npyfuncs.np_dummy_return_arg,
-        'q->q': npyfuncs.np_dummy_return_arg,
-        'Q->Q': npyfuncs.np_dummy_return_arg,
-        'f->f': npyfuncs.np_dummy_return_arg,
-        'd->d': npyfuncs.np_dummy_return_arg,
-        'F->F': npyfuncs.np_complex_conjugate_impl,
-        'D->D': npyfuncs.np_complex_conjugate_impl,
+        'b->b': builtins.real_conjugate_impl,
+        'B->B': builtins.real_conjugate_impl,
+        'h->h': builtins.real_conjugate_impl,
+        'H->H': builtins.real_conjugate_impl,
+        'i->i': builtins.real_conjugate_impl,
+        'I->I': builtins.real_conjugate_impl,
+        'l->l': builtins.real_conjugate_impl,
+        'L->L': builtins.real_conjugate_impl,
+        'q->q': builtins.real_conjugate_impl,
+        'Q->Q': builtins.real_conjugate_impl,
+        'f->f': builtins.real_conjugate_impl,
+        'd->d': builtins.real_conjugate_impl,
+        'F->F': builtins.complex_conjugate_impl,
+        'D->D': builtins.complex_conjugate_impl,
     }
 
     ufunc_db[np.exp] = {
@@ -410,8 +410,8 @@ def _fill_ufunc_db(ufunc_db):
     ufunc_db[np.arccos] = {
         'f->f': npyfuncs.np_real_acos_impl,
         'd->d': npyfuncs.np_real_acos_impl,
-        'F->F': npyfuncs.np_complex_acos_impl,
-        'D->D': npyfuncs.np_complex_acos_impl,
+        'F->F': cmathimpl.acos_impl,
+        'D->D': cmathimpl.acos_impl,
     }
 
     ufunc_db[np.arctan] = {


Test patch:
diff --git a/numba/tests/test_ufuncs.py b/numba/tests/test_ufuncs.py
--- a/numba/tests/test_ufuncs.py
+++ b/numba/tests/test_ufuncs.py
@@ -1370,7 +1370,10 @@ class _TestLoopTypes(TestCase):
     """
 
     _ufuncs = all_ufuncs[:]
-    _ufuncs.remove(np.left_shift) # has its own test class
+    # Have their own test classes
+    _ufuncs.remove(np.left_shift)
+    _ufuncs.remove(np.reciprocal)
+    _ufuncs.remove(np.power)
     _compile_flags = enable_pyobj_flags
     _skip_types = 'OegG'
 
@@ -1449,52 +1452,12 @@ def _check_ufunc_with_dtypes(self, fn, ufunc, dtypes):
         # Check each array (including inputs, to ensure they weren't
         # mutated).
         for c_arg, py_arg in zip(c_args, py_args):
-            # XXX should assertPreciseEqual() accept numpy arrays?
             prec = 'single' if c_arg.dtype.char in 'fF' else 'exact'
             prec = 'double' if c_arg.dtype.char in 'dD' else prec
-            for c, py in zip(c_arg, py_arg):
-                msg = '\n'.join(["ufunc '{0}' arrays differ ({1}):",
-                                 "args: {2}", "expected {3}", "got {4}"])
-                msg = msg.format(ufunc.__name__, c_args, prec, py_arg, c_arg)
-                try:
-                    self.assertPreciseEqual(py, c, prec=prec, msg=msg)
-                except AssertionError:
-                    if not self._is_acceptable_error(py, c):
-                        raise
-
-    def _is_acceptable_error(self, py, c):
-        """
-        Result mismatch due to differing error handling.
-        """
-        def get_leftmost_bit(dtype):
-            return dtype.type(1 << (dtype.itemsize * 8 - 1))
-
-        if np.isnan(c) or np.isnan(py):
-            if 0 in [c, py]:
-                # If one is NaN and the other is zero
-                msg = "mismatch for nan values: expected {0}; got {1}"
-                print(msg.format(py, c))
-                # This is not occurring any more
-
-        elif np.abs(c) == 0 and np.abs(py) == np.abs(c):
-            # If both are zeros and they are differ by sign
-            msg = "mismatch sign for 0: expected {0}; got {1}"
-            print(msg.format(py, c))
-            return True
-
-        elif ((c == 0 and py == get_leftmost_bit(py.dtype)) or
-                  (py == 0 and c == get_leftmost_bit(py.dtype))):
-            msg = "mismatch left-most bit: expected {0}; got {1}"
-            print(msg.format(py, c))
-            return True
-
-        elif isinstance(c, complex) and isinstance(py, complex):
-            # Recurse into real and imag of a complex
-            return (self._is_acceptable_error(c.real, py.real)
-                    and self._is_acceptable_error(c.imag, py.imag))
-
-        # Match?
-        return py == c
+            msg = '\n'.join(["ufunc '{0}' arrays differ ({1}):",
+                             "args: {2}", "expected {3}", "got {4}"])
+            msg = msg.format(ufunc.__name__, c_args, prec, py_arg, c_arg)
+            self.assertPreciseEqual(py_arg, c_arg, prec=prec, msg=msg)
 
     @classmethod
     def _check_ufunc_loops(cls, ufunc):
@@ -1525,7 +1488,8 @@ class TestLoopTypes(_TestLoopTypes):
 class TestLoopTypesIntNoPython(_TestLoopTypes):
     _compile_flags = no_pyobj_flags
     _ufuncs = supported_ufuncs[:]
-    # reciprocal needs a special test due to issue #757
+    # reciprocal and power need a special test due to issue #757
+    _ufuncs.remove(np.power)
     _ufuncs.remove(np.reciprocal)
     _ufuncs.remove(np.left_shift) # has its own test class
     _ufuncs.remove(np.right_shift) # has its own test class
@@ -1534,21 +1498,41 @@ class TestLoopTypesIntNoPython(_TestLoopTypes):
 
 TestLoopTypesIntNoPython.autogenerate()
 
-class TestLoopTypesIntReciprocalNoPython(_TestLoopTypes):
+class TestLoopTypesReciprocalNoPython(_TestLoopTypes):
     _compile_flags = no_pyobj_flags
     _ufuncs = [np.reciprocal] # issue #757
-    _required_types = 'bBhHiIlLqQ'
-    _skip_types = 'fdFDmMO' + _TestLoopTypes._skip_types
+    _required_types = 'bBhHiIlLqQfdFD'
+    _skip_types = 'mMO' + _TestLoopTypes._skip_types
+
+    def _arg_for_type(self, a_letter_type, index=0):
+        res = super(self.__class__, self)._arg_for_type(a_letter_type,
+                                                        index=index)
+        if a_letter_type in 'bBhHiIlLqQ':
+            # For integer reciprocal, avoid 0 as argument, as it triggers
+            # undefined behavior that may differ in results from Numba
+            # to the compiler used to compile NumPy.
+            res[res == 0] = 42
+        return res
+
+TestLoopTypesReciprocalNoPython.autogenerate()
+
+class TestLoopTypesPowerNoPython(_TestLoopTypes):
+    _compile_flags = no_pyobj_flags
+    _ufuncs = [np.power] # issue #757
+    _required_types = 'bBhHiIlLqQfdFD'
+    _skip_types = 'mMO' + _TestLoopTypes._skip_types
 
     def _arg_for_type(self, a_letter_type, index=0):
         res = super(self.__class__, self)._arg_for_type(a_letter_type,
                                                         index=index)
-        # avoid 0 as argument, as it triggers undefined behavior that my differ
-        # in results from numba to the compiler used to compile NumPy
-        res[res == 0] = 42
+        if a_letter_type in 'bBhHiIlLqQ' and index == 1:
+            # For integer power, avoid a negative exponent, as it triggers
+            # undefined behavior that may differ in results from Numba
+            # to the compiler used to compile NumPy
+            res[res < 0] = 3
         return res
 
-TestLoopTypesIntReciprocalNoPython.autogenerate()
+TestLoopTypesPowerNoPython.autogenerate()
 
 class TestLoopTypesIntLeftShiftNoPython(_TestLoopTypes):
     _compile_flags = no_pyobj_flags