summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/neps/nep-0050-scalar-promotion.rst125
1 files changed, 125 insertions, 0 deletions
diff --git a/doc/neps/nep-0050-scalar-promotion.rst b/doc/neps/nep-0050-scalar-promotion.rst
index 2de9100ac..0daad5638 100644
--- a/doc/neps/nep-0050-scalar-promotion.rst
+++ b/doc/neps/nep-0050-scalar-promotion.rst
@@ -120,6 +120,85 @@ following hold::
True + np.uint8(2) == np.uint8(3)
+Table comparing new and old behaviour
+-------------------------------------
+
+The following table lists relevant changes and unchanged behaviours.
+Please see the `Old implementation`_ for a detailed explenation of the rules
+that lead to the "Old results", and the following sections for the rules
+explaining the new
+Please see the backwards compatibility section for a discussion of how these
+changes are likely to impact users in practice.
+
+Note the important distinction between a 0-D array like ``array(2)`` and
+arrays that are not 0-D, such as ``array([2])``.
+
+.. list-table:: Table of changed behaviours
+ :widths: 20 12 12
+ :header-rows: 1
+
+ * - Expression
+ - Old result
+ - New result
+ * - ``uint8(1) + 2``
+ - ``int64(3)``
+ - ``uint8(3)`` [T1]_
+ * - ``array([1], uint8) + int64(1)`` or
+
+ ``array([1], uint8) + array(1, int64)``
+ - ``array([2], unit8)``
+ - ``array([2], int64)`` [T2]_
+ * - ``array([1.], float32) + float64(1.)`` or
+
+ ``array([1.], float32) + array(1., float64)``
+ - ``array([2.], float32)``
+ - ``array([2.], float64)``
+ * - ``array([1], uint8) + 1``
+ - ``array([2], uint8)``
+ - *unchanged*
+ * - ``array([1], uint8) + 200``
+ - ``array([201], np.uint8)``
+ - *unchanged*
+ * - ``array([100], uint8) + 200``
+ - ``array([ 44], uint8)``
+ - *unchanged* [T3]_
+ * - ``array([1], uint8) + 300``
+ - ``array([301], uint16)``
+ - *Exception* [T4]_
+ * - ``uint8(1) + 300``
+ - ``int64(301)``
+ - *Exception* [T5]_
+ * - ``float32(1) + 3e100``
+ - ``float64(3e100)``
+ - ``float32(Inf)`` *and* ``OverflowWarning`` [T6]_
+ * - ``array([0.1], float32) == 0.1``
+ - ``array([False])``
+ - *unchanged*
+ * - ``array([0.1], float32) == float64(0.1)``
+ - ``array([ True])``
+ - ``array([False])`` [T7]_
+ * - ``array([1.], float32) + 3``
+ - ``array([4.], float32)``
+ - *unchanged*
+ * - ``array([1.], float32) + int64(3)``
+ - ``array([4.], float32)``
+ - ``array([4.], float64)`` [T8]_
+
+.. [T1] New behaviour honours the dtype of the ``uint8`` scalar.
+.. [T2] Current NumPy ignores the precision of 0-D arrays or NumPy scalars
+ when combined with arrays.
+.. [T3] Current NumPy ignores the precision of 0-D arrays or NumPy scalars
+ when combined with arrays.
+.. [T4] Old behaviour uses ``uint16`` because ``300`` does not fit ``uint8``,
+ new behaviour raises an error for the same reason.
+.. [T5] ``300`` cannot be converted to ``uint8``.
+.. [T6] ``np.float32(3e100)`` overflows to infinity.
+.. [T7] ``0.1`` loses precision when cast to ``float32``, but old behaviour
+ casts the ``float64(0.1)`` and then matches.
+.. [T8] NumPy promotes ``float32`` and ``int64`` to ``float64``. The old
+ behaviour ignored the ``int64`` here.
+
+
Motivation and Scope
====================
@@ -338,6 +417,52 @@ The following provides some additional details on the current "value based"
promotion logic, and then on the "weak scalar" promotion and how it is handled
internally.
+.. _Old implementation:
+
+Old implementation of "values based" promotion
+----------------------------------------------
+
+This section reviews how the current value-based logic works in practice,
+please see the following section for examples on how it can be useful.
+
+When NumPy sees a "scalar" value, which can be a Python int, float, complex,
+a NumPy scalar or an array::
+
+ 1000 # Python scalar
+ int32(1000) # NumPy scalar
+ np.array(1000, dtype=int64) # zero dimensional
+
+Or the float/complex equivalents, NumPy will ignore the precision of the dtype
+and find the smallest possible dtype that can hold the value.
+That is, it will try the following dtypes:
+
+* Integral: ``uint8``, ``int8``, ``uint16``, ``int16``, ``uint32``, ``int32``,
+ ``uint64``, ``int64``.
+* Floating: ``float16``, ``float32``, ``float64``, ``longdouble``.
+* Complex: ``complex64``, ``complex128``, ``clongdouble``.
+
+Note that e.g. for the integer value of ``10``, the smallest dtype can be
+*either* ``uint8`` or ``int8``.
+
+NumPy never applied this rule when all arguments are scalar values:
+
+ np.int64(1) + np.int32(2) == np.int64(3)
+
+For integers, whether a value fits is decided precisely by whether it can
+be represented by the dtype.
+For float and complex, the a dtype is considered sufficient if:
+
+* ``float16``: ``-65000 < value < 65000`` (or NaN/Inf)
+* ``float32``: ``-3.4e38 < value < 3.4e38`` (or NaN/Inf)
+* ``float64``: ``-1.7e308 < value < 1.7e308`` (or Nan/Inf)
+* ``longdouble``: (largest range, so no limit)
+
+for complex these bounds were applied to the real and imaginary component.
+These values roughly correspond to ``np.finfo(np.float32).max``.
+(NumPy did never force the use of ``float64`` for a value of
+``float32(3.402e38)`` though, but it will for a Python value of ``3.402e38``.)
+
+
State of the current "value based" promotion
---------------------------------------------