<h3 dir="auto">Describe the issue:</h3>
<p dir="auto">Implicit type casting for binary operators seems to ignore types of scalar values, see code example below where the left operand is passed a 0D scalar and 1D array respectively.</p>
<p dir="auto">This behaviour provokes unexpected overflow errors in corner cases of empty shape tuples that can be easily overseen.</p>
<h3 dir="auto">Reproduce the code example:</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import numpy as np
type_w_scalar = (np.ones((), dtype=np.int32) + np.ones(5, dtype=np.int16)).dtype
type_wo_scalar = (np.ones(1, dtype=np.int32) + np.ones(5, dtype=np.int16)).dtype
promoted_type = np.promote_types(np.int32, np.int16)
# Note that promoted_type == type_wo_scalar != type_w_scalar
# promoted_type == np.int32
# type_wo_scalar == np.int32
# type_w_scalar == np.int16
print(type_w_scalar, type_wo_scalar, promoted_type)"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">numpy</span> <span class="pl-k">as</span> <span class="pl-s1">np</span>
<span class="pl-s1">type_w_scalar</span> <span class="pl-c1">=</span> (<span class="pl-s1">np</span>.<span class="pl-en">ones</span>((), <span class="pl-s1">dtype</span><span class="pl-c1">=</span><span class="pl-s1">np</span>.<span class="pl-s1">int32</span>) <span class="pl-c1">+</span> <span class="pl-s1">np</span>.<span class="pl-en">ones</span>(<span class="pl-c1">5</span>, <span class="pl-s1">dtype</span><span class="pl-c1">=</span><span class="pl-s1">np</span>.<span class="pl-s1">int16</span>)).<span class="pl-s1">dtype</span>
<span class="pl-s1">type_wo_scalar</span> <span class="pl-c1">=</span> (<span class="pl-s1">np</span>.<span class="pl-en">ones</span>(<span class="pl-c1">1</span>, <span class="pl-s1">dtype</span><span class="pl-c1">=</span><span class="pl-s1">np</span>.<span class="pl-s1">int32</span>) <span class="pl-c1">+</span> <span class="pl-s1">np</span>.<span class="pl-en">ones</span>(<span class="pl-c1">5</span>, <span class="pl-s1">dtype</span><span class="pl-c1">=</span><span class="pl-s1">np</span>.<span class="pl-s1">int16</span>)).<span class="pl-s1">dtype</span>
<span class="pl-s1">promoted_type</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">promote_types</span>(<span class="pl-s1">np</span>.<span class="pl-s1">int32</span>, <span class="pl-s1">np</span>.<span class="pl-s1">int16</span>)
<span class="pl-c"># Note that promoted_type == type_wo_scalar != type_w_scalar</span>
<span class="pl-c"># promoted_type == np.int32</span>
<span class="pl-c"># type_wo_scalar == np.int32</span>
<span class="pl-c"># type_w_scalar == np.int16</span>
<span class="pl-en">print</span>(<span class="pl-s1">type_w_scalar</span>, <span class="pl-s1">type_wo_scalar</span>, <span class="pl-s1">promoted_type</span>)</pre></div>
<h3 dir="auto">Error message:</h3>
<p dir="auto"><em>No response</em></p>
<h3 dir="auto">NumPy/Python version information:</h3>
<p dir="auto">1.20.3 3.8.8 (default, Feb 24 2021, 21:46:12)<br>
[GCC 7.3.0]</p>
<p dir="auto">Using direct ufunc calls below to eliminate operator overloading from the test case:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&gt;&gt;&gt; one_eps = 1.00000001
&gt;&gt;&gt; np.greater_equal(np.array(1.0, np.float32), np.array(one_eps, np.float64))
False
&gt;&gt;&gt; np.greater_equal(np.array([1.0], np.float32), np.array(one_eps, np.float64))
array([ True])  # wrong!
&gt;&gt;&gt; np.greater_equal(np.array([1.0], np.float32), np.array([one_eps], np.float64))
array([False])
&gt;&gt;&gt; np.greater_equal(np.array(1.0, np.float32), np.array([one_eps], np.float64))
array([False])"><pre class="notranslate"><span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">one_eps</span> <span class="pl-c1">=</span> <span class="pl-c1">1.00000001</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">np</span>.<span class="pl-en">array</span>(<span class="pl-c1">1.0</span>, <span class="pl-s1">np</span>.<span class="pl-s1">float32</span>), <span class="pl-s1">np</span>.<span class="pl-en">array</span>(<span class="pl-s1">one_eps</span>, <span class="pl-s1">np</span>.<span class="pl-s1">float64</span>))
<span class="pl-c1">False</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-c1">1.0</span>], <span class="pl-s1">np</span>.<span class="pl-s1">float32</span>), <span class="pl-s1">np</span>.<span class="pl-en">array</span>(<span class="pl-s1">one_eps</span>, <span class="pl-s1">np</span>.<span class="pl-s1">float64</span>))
<span class="pl-en">array</span>([ <span class="pl-c1">True</span>])  <span class="pl-c"># wrong!</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-c1">1.0</span>], <span class="pl-s1">np</span>.<span class="pl-s1">float32</span>), <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-s1">one_eps</span>], <span class="pl-s1">np</span>.<span class="pl-s1">float64</span>))
<span class="pl-en">array</span>([<span class="pl-c1">False</span>])
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">np</span>.<span class="pl-en">array</span>(<span class="pl-c1">1.0</span>, <span class="pl-s1">np</span>.<span class="pl-s1">float32</span>), <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-s1">one_eps</span>], <span class="pl-s1">np</span>.<span class="pl-s1">float64</span>))
<span class="pl-en">array</span>([<span class="pl-c1">False</span>])</pre></div>
<p dir="auto">Caused by <code class="notranslate">result_type</code> having some unusual rules:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="&gt;&gt;&gt; np.result_type(np.float32, np.float64)
dtype('float64') # ok
&gt;&gt;&gt; np.result_type(np.float32, np.float64([1]))
dtype('float64') # ok
&gt;&gt;&gt; np.result_type(np.float32, np.float64(1))
dtype('float32') # what"><pre class="notranslate"><span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">result_type</span>(<span class="pl-s1">np</span>.<span class="pl-s1">float32</span>, <span class="pl-s1">np</span>.<span class="pl-s1">float64</span>)
<span class="pl-en">dtype</span>(<span class="pl-s">'float64'</span>) <span class="pl-c"># ok</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">result_type</span>(<span class="pl-s1">np</span>.<span class="pl-s1">float32</span>, <span class="pl-s1">np</span>.<span class="pl-en">float64</span>([<span class="pl-c1">1</span>]))
<span class="pl-en">dtype</span>(<span class="pl-s">'float64'</span>) <span class="pl-c"># ok</span>
<span class="pl-c1">&gt;&gt;</span><span class="pl-c1">&gt;</span> <span class="pl-s1">np</span>.<span class="pl-en">result_type</span>(<span class="pl-s1">np</span>.<span class="pl-s1">float32</span>, <span class="pl-s1">np</span>.<span class="pl-en">float64</span>(<span class="pl-c1">1</span>))
<span class="pl-en">dtype</span>(<span class="pl-s">'float32'</span>) <span class="pl-c"># what</span></pre></div>
<p dir="auto">This seems wrong to me, but appears to be deliberate.</p>
<p dir="auto">It seems the goal here is to interpret python scalars as loosely-typed (in the presence of arrays). This handles thing like:</p>
<ul dir="auto">
<li>letting integer literals choose between signed and unsigned</li>
<li>letting number literals have smaller representations than <code class="notranslate">float64</code> and <code class="notranslate">int_</code> if they'd fit</li>
</ul>
<p dir="auto">However, the method is too greedy, and also (in the presence of arrays) discards type information from:</p>
<ol dir="auto">
<li><code class="notranslate">np.number</code> instances with an explicit type, like <code class="notranslate">np.int64(1)</code> and <code class="notranslate">np.float32(1)</code></li>
<li>0d <code class="notranslate">ndarray</code> instances, by decaying them to their scalars</li>
</ol>
<p dir="auto">The problem is that <code class="notranslate">PyArray_ResultType</code> (and the ufunc type resolver) has lost the information about where it's array arguments came from, and whether their types are explicit or implicit.</p>