Repository: chainer/chainer

Issue:
Behavior of `mod` for int is inconsistent
```
>>> x=cupy.array(0, dtype=cupy.int32)
>>> 3 % x
array(3)
>>> x=numpy.array(0, dtype=numpy.int32)
>>> 3 % x
__main__:1: RuntimeWarning: divide by zero encountered in remainder
0
```

This behavior is written in the numpy document.

> Returns 0 when x2 is 0 and both x1 and x2 are (arrays of) integers.

http://docs.scipy.org/doc/numpy/reference/generated/numpy.remainder.html



Discussion:

Resolving patch:
diff --git a/cupy/math/arithmetic.py b/cupy/math/arithmetic.py
--- a/cupy/math/arithmetic.py
+++ b/cupy/math/arithmetic.py
@@ -148,8 +148,11 @@
 remainder = elementwise.create_ufunc(
     'cupy_remainder',
     ('bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L',
-     'qq->q', 'QQ->Q', 'ee->e', 'ff->f', 'dd->d'),
-    'out0 = in0 - _floor_divide(in0, in1) * in1',
+     'qq->q', 'QQ->Q',
+     ('ee->e', 'out0 = in0 - _floor_divide(in0, in1) * in1)'),
+     ('ff->f', 'out0 = in0 - _floor_divide(in0, in1) * in1)'),
+     ('dd->d', 'out0 = in0 - _floor_divide(in0, in1) * in1)')),
+    'out0 = (in0 - _floor_divide(in0, in1) * in1) * (in1 != 0)',
     doc='''Computes the remainder of Python division elementwise.
 
     .. seealso:: :data:`numpy.remainder`


Test patch:
diff --git a/tests/cupy_tests/test_ndarray_elementwise_op.py b/tests/cupy_tests/test_ndarray_elementwise_op.py
--- a/tests/cupy_tests/test_ndarray_elementwise_op.py
+++ b/tests/cupy_tests/test_ndarray_elementwise_op.py
@@ -319,7 +319,7 @@ class TestArrayIntElementwiseOp(unittest.TestCase):
     @testing.for_int_dtypes()
     @testing.numpy_cupy_allclose()
     def check_array_scalar_op(self, op, xp, dtype, swap=False):
-        a = testing.shaped_arange((2, 3), xp, dtype)
+        a = xp.array([[0, 1, 2], [1, 0, 2]], dtype=dtype)
         if swap:
             return op(dtype(2), a)
         else:
@@ -355,11 +355,62 @@ def test_xor_scalar(self):
     def test_rxor_scalar(self):
         self.check_array_scalar_op(operator.xor, swap=True)
 
+    def test_mod_scalar(self):
+        self.check_array_scalar_op(operator.mod)
+
+    def test_rmod_scalar(self):
+        self.check_array_scalar_op(operator.mod, swap=True)
+
+    @testing.for_int_dtypes()
+    @testing.numpy_cupy_allclose()
+    def check_array_scalarzero_op(self, op, xp, dtype, swap=False):
+        a = xp.array([[0, 1, 2], [1, 0, 2]], dtype=dtype)
+        if swap:
+            return op(dtype(0), a)
+        else:
+            return op(a, dtype(0))
+
+    def test_lshift_scalarzero(self):
+        self.check_array_scalarzero_op(operator.lshift)
+
+    def test_rlshift_scalarzero(self):
+        self.check_array_scalarzero_op(operator.lshift, swap=True)
+
+    def test_rshift_scalarzero(self):
+        self.check_array_scalarzero_op(operator.rshift)
+
+    def test_rrshift_scalarzero(self):
+        self.check_array_scalarzero_op(operator.rshift, swap=True)
+
+    def test_and_scalarzero(self):
+        self.check_array_scalarzero_op(operator.and_)
+
+    def test_rand_scalarzero(self):
+        self.check_array_scalarzero_op(operator.and_, swap=True)
+
+    def test_or_scalarzero(self):
+        self.check_array_scalarzero_op(operator.or_)
+
+    def test_ror_scalarzero(self):
+        self.check_array_scalarzero_op(operator.or_, swap=True)
+
+    def test_xor_scalarzero(self):
+        self.check_array_scalarzero_op(operator.xor)
+
+    def test_rxor_scalarzero(self):
+        self.check_array_scalarzero_op(operator.xor, swap=True)
+
+    def test_mod_scalarzero(self):
+        self.check_array_scalarzero_op(operator.mod)
+
+    def test_rmod_scalarzero(self):
+        self.check_array_scalarzero_op(operator.mod, swap=True)
+
     @testing.for_int_dtypes()
     @testing.numpy_cupy_allclose()
     def check_array_array_op(self, op, xp, dtype):
-        a = testing.shaped_arange((2, 3), xp, dtype)
-        b = testing.shaped_reverse_arange((2, 3), xp, dtype)
+        a = xp.array([[0, 1, 2], [1, 0, 2]], dtype=dtype)
+        b = xp.array([[0, 0, 1], [0, 1, 2]], dtype=dtype)
         return op(a, b)
 
     def test_lshift_array(self):
@@ -392,11 +443,17 @@ def test_xor_array(self):
     def test_ixor_array(self):
         self.check_array_scalar_op(operator.ixor)
 
+    def test_mod_array(self):
+        self.check_array_scalar_op(operator.mod)
+
+    def test_imod_array(self):
+        self.check_array_scalar_op(operator.imod)
+
     @testing.for_int_dtypes()
     @testing.numpy_cupy_allclose()
     def check_array_broadcasted_op(self, op, xp, dtype):
-        a = testing.shaped_arange((2, 3), dtype=dtype)
-        b = testing.shaped_arange((2, 1), dtype=dtype)
+        a = xp.array([[0, 1, 2], [1, 0, 2], [2, 1, 0]], dtype=dtype)
+        b = xp.array([[0, 0, 1]], dtype=dtype)
         return op(a, b)
 
     def test_broadcasted_lshift(self):
@@ -429,11 +486,17 @@ def test_broadcasted_xor(self):
     def test_broadcasted_ixor(self):
         self.check_array_broadcasted_op(operator.ixor)
 
+    def test_broadcasted_mod(self):
+        self.check_array_broadcasted_op(operator.mod)
+
+    def test_broadcasted_imod(self):
+        self.check_array_broadcasted_op(operator.imod)
+
     @testing.for_int_dtypes()
     @testing.numpy_cupy_allclose()
     def check_array_doubly_broadcasted_op(self, op, xp, dtype):
-        a = testing.shaped_arange((2, 1, 3), xp, dtype)
-        b = testing.shaped_arange((3, 1), xp, dtype)
+        a = xp.array([[[0, 1, 2]], [[1, 0, 2]]], dtype=dtype)
+        b = xp.array([[0], [0], [1]], dtype=dtype)
         return op(a, b)
 
     def test_doubly_broadcasted_lshift(self):
@@ -450,3 +513,6 @@ def test_doubly_broadcasted_or(self):
 
     def test_doubly_broadcasted_xor(self):
         self.check_array_doubly_broadcasted_op(operator.xor)
+
+    def test_doubly_broadcasted_mod(self):
+        self.check_array_doubly_broadcasted_op(operator.mod)