Repository: numpy/numpy

Issue:
Division broken when dtype specified
In a head build using python 3:

``` python
>>> np.__version__
'1.8.0.dev-069e9b0'
>>> np.divide(.4, 1.0, dtype=np.float)
0.0
>>> np.divide(.4, 1.0)
0.40000000000000002
```



Discussion:
I can reproduce with python 3.2 and numpy _1.6.2_.

On Fri, Jun 28, 2013 at 11:00 PM, justinvf notifications@github.com wrote:

> In a head build using python 3:
> 
> > > > np.**version**'1.8.0.dev-069e9b0'>>> np.divide(.4, 1.0, dtype=np.float)0.0>>> np.divide(.4, 1.0)0.40000000000000002
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/numpy/numpy/issues/3484
> .

tested: it works in 1.7.1 in python 2.7.5, but does not work in 1.7.1 in python 3.3.2, so definitely a python3 issue.

you get the same behavior in python2 if you use true_divide instead of divide, the generated signatures for that are not handled properly and it does a BYTE_divide.
python3 is completely broken because it always uses true_divide.

This just bit me in numpy 1.9.1:

``` python
# python 2
>>> np.divide(82.5, 10.0, dtype='f8')
8.25

# python 3
>>> np.divide(82.5, 10.0, dtype='f8')
8.1999999999999993
```

Whoa, that's bad. Gives an error message in 1.10-devel, but still

```
>>> np.divide(82.5, 10.)
8.25
>>> np.divide(82.5, 10., dtype='f8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: found a loop for ufunc 'true_divide' matching the type-tuple, but the inputs and/or outputs could not be cast according to the casting rule
```

It should not be working in integers just because of `'f8'`. This looks like more than a simple bug... The error message is also incomplete.

The problem is somewhere in `type_tuple_type_resolver`. And specific to `dtype='f8'`.

```
In [3]: true_divide(82.5, 10., dtype='f4')
Out[3]: 8.25

In [4]: true_divide(82.5, 10., dtype='f8')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-567cf519068c> in <module>()
----> 1 true_divide(82.5, 10., dtype='f8')

TypeError: found a loop for ufunc 'true_divide' matching the type-tuple, but the inputs and/or outputs could not be cast according to the casting rule
```

The reason double is special is that there are `true_divide` loops with integer inputs and double output.

there are still problems with division: https://stackoverflow.com/questions/45278112/numpy-integer-division-sometimes-yields-wrong-results-when-casted
```python
>>> np.true_divide(200, 100, dtype=np.float64)
-0.56000000000000005
>>> np.true_divide(200, 100, dtype=np.float32)
2.0
```
python 3, numpy 1.13.1
Confirmed, and there's a more detailed diagnosis in the stackoverflow link.
Goes through a byte type I think
```
In [6]: int8(200)
Out[6]: -56
```
There is a check somewhere that is incorrect. For laughs I changed the default output to float32 ...
```
In [2]: np.true_divide(200, 100, dtype=float64)
Out[2]: 2.0

In [3]: np.true_divide(200, 100, dtype=float32)
Out[3]: 1.951564e-20

In [4]: np.true_divide(200, 100, dtype=float16)
Out[4]: 2.0
```
Note that now it is now the float32 type that has problems. Frankly, I'm surprised that it worked at all.
I think the problem is in the handling of casting due to
```
NPY_DEFAULT_ASSIGN_CASTING = NPY_SAME_KIND_CASTING
```

Note that the `type_tuple_type_resolver`, which is in play here, doesn't allow specification of the input type casting.
Yep, that's it. Changing https://github.com/numpy/numpy/blob/master/numpy/core/src/umath/ufunc_type_resolution.c#L2175 to
```
                    NPY_SAFE_CASTING, casting,
```
Fixes the problem. Probably the call should be changed so that `input_casting` is passed.

Of course, that breaks a lot of other things. What we have here is a classic cluster f***. Looks like the `type_tuple***` resolvers need a rewrite.
Turns out that #5963 was not a correct fix.

Which is to say, the current problem was previously there, it just didn't get fixed.
Another curiousity, casting `int64` to `float16` is regarded as valid with "same_kind" casting, which is why
```
In [1]: true_divide(11100000, 100000, dtype=float16)
/home/charris/.local/bin/ipython:1: RuntimeWarning: invalid value encountered in true_divide
  #!/usr/bin/python
Out[1]: nan
```

What is going on is that if the dtype is specified, a valid loop must have an output type that matches exactly. For float16 that is "ee->e", and since same_kind casting of int64 to float16 is valid, away we go. Now if we specify `dtype=float64`, the first match has signature `bb->d`, and since same_kind casting allows casting int64 to int8, voila, the wrong answer. 

Trying to change the behavior is tricky because much current code assumes the current behavior.
I think the only change that dosen't break the world is to remove all the int loops for true_divide.

Resolving patch:
diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py
--- a/numpy/core/code_generators/generate_umath.py
+++ b/numpy/core/code_generators/generate_umath.py
@@ -314,9 +314,7 @@ def english_upper(s):
 'true_divide':
     Ufunc(2, 1, None, # One is only a unit to the right, not the left
           docstrings.get('numpy.core.umath.true_divide'),
-          'PyUFunc_DivisionTypeResolver',
-          TD('bBhH', out='d'),
-          TD('iIlLqQ', out='d'),
+          'PyUFunc_TrueDivisionTypeResolver',
           TD(flts+cmplx),
           [TypeDescription('m', FullTypeDescr, 'mq', 'm'),
            TypeDescription('m', FullTypeDescr, 'md', 'm'),


Test patch:
diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py
--- a/numpy/core/tests/test_ufunc.py
+++ b/numpy/core/tests/test_ufunc.py
@@ -1,5 +1,8 @@
 from __future__ import division, absolute_import, print_function
 
+import warnings
+import itertools
+
 import numpy as np
 import numpy.core.umath_tests as umt
 import numpy.core.operand_flag_tests as opflag_tests
@@ -7,7 +10,7 @@
 from numpy.testing import (
     TestCase, run_module_suite, assert_, assert_equal, assert_raises,
     assert_array_equal, assert_almost_equal, assert_array_almost_equal,
-    assert_no_warnings
+    assert_no_warnings, assert_allclose,
 )
 
 
@@ -353,14 +356,78 @@ def test_forced_sig(self):
         assert_equal(b, [0, 0, 1])
 
     def test_true_divide(self):
-        # True_divide has a non uniform signature, see #3484.
-        # This also tests type_tuple_type_resolver.
-        a = np.full(5, 12.5)
-        b = np.full(5, 10.0)
-        tgt = np.full(5, 1.25)
-        assert_almost_equal(np.true_divide(a, b, dtype=np.float64), tgt)
-        assert_almost_equal(np.true_divide(a, b, dtype=np.float32), tgt)
-        assert_raises(TypeError, np.true_divide, a, b, dtype=np.int)
+        a = np.array(10)
+        b = np.array(20)
+        tgt = np.array(0.5)
+
+        for tc in 'bhilqBHILQefdgFDG':
+            dt = np.dtype(tc)
+            aa = a.astype(dt)
+            bb = b.astype(dt)
+
+            # Check result value and dtype.
+            for x, y in itertools.product([aa, -aa], [bb, -bb]):
+
+                # Check with no output type specified
+                if tc in 'FDG':
+                    tgt = complex(x)/complex(y)
+                else:
+                    tgt = float(x)/float(y)
+
+                res = np.true_divide(x, y)
+                rtol = max(np.finfo(res).resolution, 1e-15)
+                assert_allclose(res, tgt, rtol=rtol)
+
+                if tc in 'bhilqBHILQ':
+                    assert_(res.dtype.name == 'float64')
+                else:
+                    assert_(res.dtype.name == dt.name )
+
+                # Check with output type specified.  This also checks for the
+                # incorrect casts in issue gh-3484 because the unary '-' does
+                # not change types, even for unsigned types, Hence casts in the
+                # ufunc from signed to unsigned and vice versa will lead to
+                # errors in the values.
+                for tcout in 'bhilqBHILQ':
+                    dtout = np.dtype(tcout)
+                    assert_raises(TypeError, np.true_divide, x, y, dtype=dtout)
+
+                for tcout in 'efdg':
+                    dtout = np.dtype(tcout)
+                    if tc in 'FDG':
+                        # Casting complex to float is not allowed
+                        assert_raises(TypeError, np.true_divide, x, y, dtype=dtout)
+                    else:
+                        tgt = float(x)/float(y)
+                        rtol = max(np.finfo(dtout).resolution, 1e-15)
+                        atol = max(np.finfo(dtout).tiny, 3e-308)
+                        # Some test values result in invalid for float16.
+                        with np.errstate(invalid='ignore'):
+                            res = np.true_divide(x, y, dtype=dtout)
+                        if not np.isfinite(res) and tcout == 'e':
+                            continue
+                        assert_allclose(res, tgt, rtol=rtol, atol=atol)
+                        assert_(res.dtype.name == dtout.name)
+
+                for tcout in 'FDG':
+                    dtout = np.dtype(tcout)
+                    tgt = complex(x)/complex(y)
+                    rtol = max(np.finfo(dtout).resolution, 1e-15)
+                    atol = max(np.finfo(dtout).tiny, 3e-308)
+                    res = np.true_divide(x, y, dtype=dtout)
+                    if not np.isfinite(res):
+                        continue
+                    assert_allclose(res, tgt, rtol=rtol, atol=atol)
+                    assert_(res.dtype.name == dtout.name)
+
+        # Check booleans
+        a = np.ones((), dtype=np.bool_)
+        res = np.true_divide(a, a)
+        assert_(res == 1.0)
+        assert_(res.dtype.name == 'float64')
+        res = np.true_divide(~a, a)
+        assert_(res == 0.0)
+        assert_(res.dtype.name == 'float64')
 
     def test_sum_stability(self):
         a = np.ones(500, dtype=np.float32)