Repository: numba/numba

Issue:
-(0 == 0) returns 65535 in numba (0.35.0)
I use Python 3.6.0 or 3.6.2 and numba (0.35.0). I checked this example at multiple environments.
The test code shows that -(0 == 0) returns 65535.

I believe this behavior is not consistent with the original python implementation and the fact that  numba returns -1 for -True (see test_use_true() )


```

In [1]: import numba as nb
In [2]: def test():
   ...:     return -(0 == 0)
   ...: 
In [3]: test()
Out[3]: -1

In [4]: @nb.njit
   ...: def test_nb():
   ...:     return -(0 == 0)
   ...: 
In [5]: test_nb()
Out[5]: 65535

In [6]: @nb.njit
   ...: def test_use_true():
   ...:     return -True
   ...: 
In [7]: test_use_true()
Out[7]: -1

```


The code below is my quick-fix.

```
In [2]: @nb.njit
   ...: def test_nb_quickfix():
   ...:     return -1.*(0==0)
   ...: 
In [3]: test_nb_quickfix()
Out[3]: -1.0
```

-(0 == 0) returns 65535 in numba (0.35.0)
I use Python 3.6.0 or 3.6.2 and numba (0.35.0). I checked this example at multiple environments.
The test code shows that -(0 == 0) returns 65535.

I believe this behavior is not consistent with the original python implementation and the fact that  numba returns -1 for -True (see test_use_true() )


```

In [1]: import numba as nb
In [2]: def test():
   ...:     return -(0 == 0)
   ...: 
In [3]: test()
Out[3]: -1

In [4]: @nb.njit
   ...: def test_nb():
   ...:     return -(0 == 0)
   ...: 
In [5]: test_nb()
Out[5]: 65535

In [6]: @nb.njit
   ...: def test_use_true():
   ...:     return -True
   ...: 
In [7]: test_use_true()
Out[7]: -1

```


The code below is my quick-fix.

```
In [2]: @nb.njit
   ...: def test_nb_quickfix():
   ...:     return -1.*(0==0)
   ...: 
In [3]: test_nb_quickfix()
Out[3]: -1.0
```

-(0 == 0) returns 65535 in numba (0.35.0)
I use Python 3.6.0 or 3.6.2 and numba (0.35.0). I checked this example at multiple environments.
The test code shows that -(0 == 0) returns 65535.

I believe this behavior is not consistent with the original python implementation and the fact that  numba returns -1 for -True (see test_use_true() )


```

In [1]: import numba as nb
In [2]: def test():
   ...:     return -(0 == 0)
   ...: 
In [3]: test()
Out[3]: -1

In [4]: @nb.njit
   ...: def test_nb():
   ...:     return -(0 == 0)
   ...: 
In [5]: test_nb()
Out[5]: 65535

In [6]: @nb.njit
   ...: def test_use_true():
   ...:     return -True
   ...: 
In [7]: test_use_true()
Out[7]: -1

```


The code below is my quick-fix.

```
In [2]: @nb.njit
   ...: def test_nb_quickfix():
   ...:     return -1.*(0==0)
   ...: 
In [3]: test_nb_quickfix()
Out[3]: -1.0
```



Discussion:
Thanks for the report. I think there are two problems here:
1. That it produces the wrong answer.
2. That `test_nb()` produces an inconsistent wrong answer across repeated runs.

For 2. I think the unary minus call typing is returning a seemingly random inferred type. I'm fairly sure this is because it relies on the definition of `numba.typing.builtins.UnaryNegate` which has signatures assembled from a `frozenset` of types inherited from `UnaryOp`. I think the types need sorting from low to high or at least an iteration order guaranteed. @sklam as this impacts type inference do you have a view?

Once 2. is fixed this would yield a consistent wrong answer.

To fix 1. I think the type signature for booleans needs adding to the `UnaryOp` signature specification and then lowering for `unary negate(bool)`.
Thanks for the report. I think there are two problems here:
1. That it produces the wrong answer.
2. That `test_nb()` produces an inconsistent wrong answer across repeated runs.

For 2. I think the unary minus call typing is returning a seemingly random inferred type. I'm fairly sure this is because it relies on the definition of `numba.typing.builtins.UnaryNegate` which has signatures assembled from a `frozenset` of types inherited from `UnaryOp`. I think the types need sorting from low to high or at least an iteration order guaranteed. @sklam as this impacts type inference do you have a view?

Once 2. is fixed this would yield a consistent wrong answer.

To fix 1. I think the type signature for booleans needs adding to the `UnaryOp` signature specification and then lowering for `unary negate(bool)`.
Thanks for the report. I think there are two problems here:
1. That it produces the wrong answer.
2. That `test_nb()` produces an inconsistent wrong answer across repeated runs.

For 2. I think the unary minus call typing is returning a seemingly random inferred type. I'm fairly sure this is because it relies on the definition of `numba.typing.builtins.UnaryNegate` which has signatures assembled from a `frozenset` of types inherited from `UnaryOp`. I think the types need sorting from low to high or at least an iteration order guaranteed. @sklam as this impacts type inference do you have a view?

Once 2. is fixed this would yield a consistent wrong answer.

To fix 1. I think the type signature for booleans needs adding to the `UnaryOp` signature specification and then lowering for `unary negate(bool)`.

Resolving patch:
diff --git a/numba/targets/numbers.py b/numba/targets/numbers.py
--- a/numba/targets/numbers.py
+++ b/numba/targets/numbers.py
@@ -487,12 +487,30 @@ def int_sign_impl(context, builder, sig, args):
     return impl_ret_untracked(context, builder, sig.return_type, res)
 
 
+def bool_negate_impl(context, builder, sig, args):
+    [typ] = sig.args
+    [val] = args
+    res = context.cast(builder, val, typ, sig.return_type)
+    res = builder.neg(res)
+    return impl_ret_untracked(context, builder, sig.return_type, res)
+
+
+def bool_unary_positive_impl(context, builder, sig, args):
+    [typ] = sig.args
+    [val] = args
+    res = context.cast(builder, val, typ, sig.return_type)
+    return impl_ret_untracked(context, builder, sig.return_type, res)
+
+
 lower_builtin('==', types.boolean, types.boolean)(int_eq_impl)
 lower_builtin('!=', types.boolean, types.boolean)(int_ne_impl)
 lower_builtin('<', types.boolean, types.boolean)(int_ult_impl)
 lower_builtin('<=', types.boolean, types.boolean)(int_ule_impl)
 lower_builtin('>', types.boolean, types.boolean)(int_ugt_impl)
 lower_builtin('>=', types.boolean, types.boolean)(int_uge_impl)
+lower_builtin('-', types.boolean)(bool_negate_impl)
+lower_builtin('+', types.boolean)(bool_unary_positive_impl)
+
 
 @lower_builtin('==', types.Const, types.Const)
 def const_eq_impl(context, builder, sig, args):
diff --git a/numba/typing/builtins.py b/numba/typing/builtins.py
--- a/numba/typing/builtins.py
+++ b/numba/typing/builtins.py
@@ -297,16 +297,17 @@ class BitwiseInvert(ConcreteTemplate):
     # while Python returns an int.  This makes it consistent with
     # np.invert() and makes array expressions correct.
     cases = [signature(types.boolean, types.boolean)]
-    cases += [signature(choose_result_int(op), op) for op in types.unsigned_domain]
-    cases += [signature(choose_result_int(op), op) for op in types.signed_domain]
+    cases += [signature(choose_result_int(op), op) for op in sorted(types.unsigned_domain)]
+    cases += [signature(choose_result_int(op), op) for op in sorted(types.signed_domain)]
 
     unsafe_casting = False
 
 class UnaryOp(ConcreteTemplate):
-    cases = [signature(choose_result_int(op), op) for op in types.unsigned_domain]
-    cases += [signature(choose_result_int(op), op) for op in types.signed_domain]
+    cases = [signature(choose_result_int(op), op) for op in sorted(types.unsigned_domain)]
+    cases += [signature(choose_result_int(op), op) for op in sorted(types.signed_domain)]
     cases += [signature(op, op) for op in sorted(types.real_domain)]
     cases += [signature(op, op) for op in sorted(types.complex_domain)]
+    cases += [signature(types.intp, types.boolean)]
 
 
 @infer


Test patch:
diff --git a/numba/tests/test_operators.py b/numba/tests/test_operators.py
--- a/numba/tests/test_operators.py
+++ b/numba/tests/test_operators.py
@@ -1110,7 +1110,7 @@ def test_not_npm(self):
             cfunc = cres.entry_point
             self.assertEqual(pyfunc(val), cfunc(val))
 
-    # XXX test_negate should check for negative and positive zeros and infinites
+    # XXX test_negate should check for negative and positive zeros and infinities
 
     @tag('important')
     def test_negate_npm(self):
@@ -1123,6 +1123,8 @@ def test_negate_npm(self):
             types.float32,
             types.float64,
             types.complex128,
+            types.boolean,
+            types.boolean,
         ]
         values = [
             1,
@@ -1131,12 +1133,15 @@ def test_negate_npm(self):
             1.2,
             2.4,
             3.4j,
+            True,
+            False,
         ]
         for ty, val in zip(argtys, values):
             cres = compile_isolated(pyfunc, [ty])
             cfunc = cres.entry_point
             self.assertAlmostEqual(pyfunc(val), cfunc(val))
 
+
     def test_negate(self):
         pyfunc = self.op.negate_usecase
         values = [
@@ -1145,6 +1150,8 @@ def test_negate(self):
             3,
             1.2,
             3.4j,
+            True,
+            False,
         ]
         cres = compile_isolated(pyfunc, (), flags=force_pyobj_flags)
         cfunc = cres.entry_point
@@ -1161,6 +1168,8 @@ def test_unary_positive_npm(self):
             types.float32,
             types.float64,
             types.complex128,
+            types.boolean,
+            types.boolean,
         ]
         values = [
             1,
@@ -1169,6 +1178,8 @@ def test_unary_positive_npm(self):
             1.2,
             2.4,
             3.4j,
+            True,
+            False
         ]
         for ty, val in zip(argtys, values):
             cres = compile_isolated(pyfunc, [ty])
@@ -1183,6 +1194,8 @@ def test_unary_positive(self):
             3,
             1.2,
             3.4j,
+            True,
+            False,
         ]
         cres = compile_isolated(pyfunc, (), flags=force_pyobj_flags)
         cfunc = cres.entry_point