From fa9c087fb9679b3f6fc76dec76f2d16cf4defb15 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 5 Dec 2023 09:15:36 +0300 Subject: [PATCH 01/10] Initial version --- peps/pep-9999.rst | 659 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 659 insertions(+) create mode 100644 peps/pep-9999.rst diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst new file mode 100644 index 00000000000..c4c1cb5c231 --- /dev/null +++ b/peps/pep-9999.rst @@ -0,0 +1,659 @@ +PEP: 9999 +Title: Imaginary type and IEC 60559-compatible complex arithmetic +Author: Sergey B Kirpichev +Sponsor: Pending +Discussions-To: Pending +Status: Draft +Type: Standards Track +Created: 31-Oct-2025 +Python-Version: 3.15 +Post-History: `10-Sep-2023 `__, + `14-Jan-2025 `__ + + +Abstract +======== + +This PEP alters meaning of the *imaginary literal* in Python. From now it will +denote a newly added :external+py3.14:class:`complex` *subtype* (called therein +imaginary or pure-imaginary complex number) with a zero real component. This +component of imaginary numbers will be *ignored* in arithmetic operations when +other operand is either a :external+py3.14:class:`complex` (but not imaginary) +or a :external+py3.14:class:`float`. + + +Motivation +========== + +Complex numbers were part of the Python language from very early releases +(since v1.2 in the stdlib and since v1.3 as the +:external+py3.14:class:`complex` builtin). But until recently their +implementation was simplistic, based on approach (dating back to the Fortran +66) that just adds complex data type as ordered pair of +:external+py3.14:class:`float` numbers, doing arithmetic on them by usual +formulae. When operands are of mixed types, this assume [1]_ we first do type +coersions, i.e. conversion of integers and floating-point numbers to complex +numbers with zero (0.0) imaginary component. The imaginary unit (``1j`` in +Python) in such system is a complex number with zero real component. + +Unfortunately, this doesn't work well, if components of the complex number are +floating-point numbers, used as model of the *extended* real line. Such as +ones, specified by the IEC 60559 standard, which beyond normal +(finite and nonzero) numbers has special values like signed zero, infinities +and nans. Lets take simple examples with multiplication (where +:math:`\rightarrow` denotes type coersion and :math:`\Rightarrow` is +substitution of :math:`0.0+i` for :math:`i`): + +.. math:: + :label: ex-mul1 + + 2.0 \times (\infty + 3.0 i) & \rightarrow (2.0 + 0.0 i) \times (\infty + 3.0 i) \\ + & = (2.0\times\infty - 0.0\times 3.0) + i(0.0\times\infty + 2.0\times 3.0) \\ + & = \infty + i\mathrm{nan} + +.. math:: + :label: ex-mul2 + + 2.0 i \times (\infty + 3.0 i) & \Rightarrow (0.0 + 2.0 i) \times (\infty + 3.0 i) \\ + & = (0.0\times\infty - 2.0\times 3.0) + i (0.0\times 3.0 + 2.0\times\infty) \\ + & = \mathrm{nan} + i\infty + +.. math:: + :label: ex-mul3 + + 2.0 i \times (-0.0 + 3.0 i) & \Rightarrow (0.0 + 2.0 i) \times (-0.0 + 3.0 i) \\ + & = (-0.0\times 0.0 - 2.0\times 3.0) + i (-2.0\times 0.0 + 0.0\times 3.0) \\ + & = -6.0 + i 0.0 + +Users with some mathematical background instead would expect, that ``x+yj`` +notation in the Python is just usual rectangular form for a complex number +with components ``x`` (real) and ``y`` (imaginary) and that above examples +will work, following rules of elementary algebra (with assumption that +:math:`i` is a symbol such that :math:`i^2=-1`): + +.. math:: + + 2.0 \times (\infty + 3.0 i) & = 2.0\times\infty + i 2.0\times 3.0 = \infty + i 6.0 \\ + 2.0 i \times (\infty + 3.0 i) & = -2.0\times 3.0 + i 2.0\times\infty = -6.0 + i\infty \\ + 2.0 i \times (-0.0 + 3.0 i) & = -2.0\times 3.0 - i 2.0\times 0.0 = -6.0 - i 0.0 \\ + + +Same affects addition (or subtraction): + +.. math:: + :label: ex-add1 + + 2.0 - 0.0 i & \rightarrow (2.0 + 0.0 i) - 0.0 i \\ + & \Rightarrow (2.0 + 0.0 i) - (0.0 + 0.0 i) \\ + & = 2.0 + 0.0 i + +.. math:: + :label: ex-add2 + + -0.0 + 2.0 i & \rightarrow (-0.0 + 0.0 i) + 2.0 i \\ + & \Rightarrow (-0.0 + 0.0 i) + (0.0 + 2.0 i) \\ + & = 0.0 + 2.0 i + +Simplistic approach for complex arithmetic is underlying reason for numerous +and reccuring issues in the CPython bugtracker (here is an incomplete list: +`#61538 `_, +`#66738 `_, +`#67418 `_, +`#69639 `_, +`#70026 `_, +`#71550 `_, +`#84450 `_, +`#85657 `_, +`#105027 `_, +`#107854 `_, +`#112176 `_, +`#122615 `_) +and mathematical libraries in the Python ecosystem (e.g. `numpy/numpy#26310 +`_ or `mpmath/mpmath#774 +`_). +Among others, broken ``repr(eval(repr(x))) == repr(x)`` invariant for complex +numbers: + +.. code:: pycon + + >>> -0.0+1j + 1j + >>> complex(-0.0, 1) # note funny signed integer zero + (-0+1j) + + +To workaround described behavior, it's required to check operands of +arithmetic expressions and handle special numbers separately. E.g. you can't +just blindly take analytic identity from the textbook and use it to implement +some mathematical function. Lets see how this happens already in the stdlib +on `cmath.asin() +`_ +example. Here is a pure-Python version of same code: + +.. code:: python + + def asin(z): + # asin(z) = -i asinh(iz) + z = complex(-z.imag, z.real) # z -> iz + z = cmath.asinh(z) + return complex(z.imag, -z.real) # z -> -iz + +Note that here we are essentially doing component-wise computations, complex +arithmetic is not used at all. In other words, being simple to implement --- +it's less useful to end users. + +A more modern approach [2]_, reflecting advances in the IEC standard for real +floating-point arithmetic, instead avoid coersion of reals to complexes +(:math:`\rightarrow`) and use a separate data type (imaginary) to represent +the imaginary unit, *ignoring it's real component in arithmetic* (i.e. no +implicit cast (:math:`\Rightarrow`) to a complex number with zero real part). +The ``cmath.asin()`` would be implemented with this approach simply by: + +.. code:: python + + def asin(z): + return -1j*cmath.asinh(1j*z) + + +It's pioneered by the C99 standard ([3]_, [4]_). This is also how complex +arithmetic implemented in the Ada language [5]_. Some mathematical libraries +(like the GNU GSL [6]_) or the GNU MPC [7]_) have special routines to implement +mixed-mode arithmetic for complex numbers, i.e. when one operand either +pure-real or pure-imaginary. As a side effect, this also introduce some +performance boost for operations with mixed types (e.g. multiplication of +complex and real numbers costs only two real multiplications, not four). +Though it's more important, that in the IEC floating-point arithmetic results +here are uniquely determined by usual mathematical formulae. + +For a first step, `PR#124829 `__ +added in the CPython 3.14 mixed-mode rules for complex arithmetic, combining +real and complex operands. So, examples like :eq:`ex-mul1` or :eq:`ex-add1` +now are working correctly: + +.. code:: pycon + + >>> from cmath import inf + >>> 2*(inf+3j) + (inf+6j) + >>> 2-0j + (2-0j) + + +Unfortunately, this is only a half-way solution. To fix the rest of above +examples we need a separate type for pure-imaginary complex numbers. + + +Rationale +========= + +Lets collect here some arguments (with possible answers) against adoption of +newer approach to complex arithmetic in Python. + + +Special cases are rare +---------------------- + +That's not true, as special numbers coming not just from input, but also +*during* computations (e.g. with underflow or overflow). Robust software must +account for them and currently this usually require to reinvent complex +arithmetic in application, i.e. use the :external+py3.14:class:`complex` type +just as a bag for it's components. The only known cure for this is presented +by the PEP: + + Generally, mixed-mode arithmetic combining real and complex variables + should be performed directly, not by first coercing the real to complex, + lest the sign of zero be rendered uninformative; the same goes for + combinations of pure imaginary quantities with complex variables. + + -- Kahan, W: Branch cuts for complex elementary functions. + + +The complex facility should be simple +------------------------------------- + +Simplicity is a goal, but most importantly for the end user, not implementators +of the complex arithmetic. + + +Upcoming C2y standard abandon [8]_ the imaginary type +----------------------------------------------------- + +That might be viewed as a failure of the new approach: no compiler from major +players had correct implementation of the C99 Annex G. + +On another hand, this might be also viewed as an indication of poor adoption of +the Annex G itself. Notably, the MSVC miss one. So, neither the CPython, nor +any other Python implementation (per author knowledge) uses native complex +arithmetic from the C language, and hardly things will be changed soon. + +It's also important to note, that removal documents from the C language +commetee don't discuss mathematical arguments for the imaginary types at all +[9]_. + + +Specification +============= + +All imaginary values could be represented as ``x*1j``, where ``x`` is some +:external+py3.14:class:`float` number. So, strictly speaking, we need only +one such object, the imaginary unit ``1j``, with a property + +.. code:: + + 1j*1j == -1.0 + + +Arbitrary complex value is a direct sum of a pure-real +(:external+py3.14:class:`float` number) part and a pure-imaginary complex +number and following identities holds (assuming ``x`` and ``y`` are +:external+py3.14:class:`float`'s): + +.. code:: python + + complex(x, y) == x + y*1j + repr(complex(x, y)) == repr(x + y*1j) + + +Tables below define unary operations, additive operators (binary ``+`` and +``-``) and multiplicative operators (``*`` and ``/``) for all possible +combinations of types (integer operand values will be implicitly converted to +:external+py3.14:class:`float`'s). In all cases the result approximate the +real and imaginary parts, respectively, of the mathematical formula to be +computed. + +.. table:: Unary operations + :align: left + + +--------+--------+--------------+---------------+-------------+ + | z | +z | -z | z.conjugate() | abs(z) | + +--------+--------+--------------+---------------+-------------+ + | x | x | -x | x | abs(x) | + +--------+--------+--------------+---------------+-------------+ + | yj | yj | (-y)j | (-y)j | abs(y) | + +--------+--------+--------------+---------------+-------------+ + | x + yj | x + yj | (-x) + (-y)j | x + (-y)j | hypot(x, y) | + +--------+--------+--------------+---------------+-------------+ + + +.. table:: Addition and subtraction + :align: left + + +----------+------------+--------------+--------------------+ + | ± | u | vj | u + vj | + +----------+------------+--------------+--------------------+ + | x | x ± u | x + (±v)j | (x ± u) + (±v)j | + +----------+------------+--------------+--------------------+ + | yj | ±u + yj | (y ± v)j | ±u + (y ± v)j | + +----------+------------+--------------+--------------------+ + | x + yj | x ± u + yj | x + (y ± v)j | (x ± u) + (y ± v)j | + +----------+------------+--------------+--------------------+ + + +If both operands have imaginary type, then the result has imaginary type. If +one operand has real type and the other operand has imaginary type, or if +either operand has complex type, then the result has complex type. + + +.. table:: Multiplication + :align: left + + +----------+----------------+-----------------+----------------------------+ + | ``*`` | u | vj | u + vj | + +----------+----------------+-----------------+----------------------------+ + | x | x*u | (x*v)j | (x*u) + (x*v)j | + +----------+----------------+-----------------+----------------------------+ + | yj | (y*u)j | -y*v | (-y*v) + (y*u)j | + +----------+----------------+-----------------+----------------------------+ + | x + yj | (x*u) + (y*u)j | (-y*v) + (x*v)j | (x*u - y*v) + (y*u + x*v)j | + +----------+----------------+-----------------+----------------------------+ + + +.. table:: Division (assuming ``w = u**2 + v**2``) + :align: left + + +----------+----------------+---------------+----------------------------------+ + | / | u | vj | u + vj | + +----------+----------------+---------------+----------------------------------+ + | x | x/u | (-x/v)j | x*u/w + (-x*v/w)j | + +----------+----------------+---------------+----------------------------------+ + | yj | (y/u)j | y/v | y*v/w + (y*u/w)j | + +----------+----------------+---------------+----------------------------------+ + | x + yj | (x/u) + (y/u)j | y/v + (-x/v)j | (x*u + y*v)/w + ((y*u - x*v)/w)j | + +----------+----------------+---------------+----------------------------------+ + + +If one operand has real type and the other operand has imaginary type, then the +result has imaginary type. If both operands have imaginary type, then the +result has real type. If either operand has complex type, then the result has +complex type. + +This specification do not indicate how exactly the results are to be evaluated +[10]_ for complex multiplication (when *both* operands are complex numbers) and +for division when the right operand is a complex number. Though, if +implementation of floating-point arithmetic support the IEC 60559 +floating-point standard, results of all mixed-mode operations, except for +division, are specified above unambigously and it's also expected that +multiplication always must be commutative, and that division compute result +without undue overflow or underflow. + +The ``*`` and ``/`` operators satisfy the following infinity properties for +all real, imaginary, and complex operands: + + - if one operand is an infinity and the other operand is a nonzero finite + number or an infinity, then the result of the ``*`` operator is an + infinity; + + - if the first operand is an infinity and the second operand is a finite + number, then the result of the ``/`` operator is an infinity; + + - if the first operand is a finite number and the second operand is an + infinity, then the result of the ``/`` operator is a zero; + + - if the first operand is a nonzero finite number or an infinity and the + second operand is a zero, then the result of the ``/`` operator is an + infinity. + + +Imaginary and complex numbers will have disinct string representations: + +.. code:: python + + repr(complex(x, y)) = ("(" + + format(x, ".0f" if x and x.is_integer() else "") + + ("+" if math.copysign(1, y) == 1 else "") + + repr(y*1j) + ")") + repr(x*1j) = (repr(x) + "j").replace(".0j", "j") + +Parsing strings with the integer "negative zero" in real part (i.e. ``"-0+1j"`` +or ``"(-0+1j)"``) will be deprecated. + + +New C-API +--------- + +.. c:type:: PyImaginaryObject + + This subtype of :c:type:`PyComplexObject` represents a Python imaginary + number object. + + +.. c:var:: PyTypeObject PyImaginary_Type + + This instance of :c:type:`PyTypeObject` represents purely imaginary numbers, + the Python complex number type *without* real component. + + +.. c:function:: int PyImaginary_Check(PyObject *p) + + Return true if its argument is a :c:type:`PyImaginaryObject` or a subtype of + :c:type:`PyImaginaryObject`. This function always succeeds. + + +.. c:function:: int PyImaginary_CheckExact(PyObject *p) + + Return true if its argument is a :c:type:`PyImaginaryObject`, but not a + subtype of :c:type:`PyImaginaryObject`. This function always succeeds. + + +.. c:function:: PyObject* PyImaginary_FromDouble(double imag) + + Return a new :c:type:`PyImaginaryObject` object with *imag* imaginary + component. Return ``NULL`` with an exception set on error. + + Imaginary component value of a new object could be taken with + :c:func:`PyComplex_ImagAsDouble`. + + +In conformance with recent C-API group `decision +`__ we don't offer API +to do arithmetic on low-level representation of complex numbers in CPython. +Instead, it's expected that C-API users either will use `PyNumber_* +`__ API or will export numbers +from Python objects and do arithmetic with some external library (like the GNU +GSL), then import back. + + +It's Not Magic +============== + +New arithmetic rules correct some more examples, where using known analytic +identities produced wrong results. Here an example with :func:`~cmath.atan` +near branch cut: + +.. code:: pycon + + >>> import cmath + >>> z = 2j - 0 # or complex(-0.0, 2) + >>> cmath.atan(z) + (-1.5707963267948966+0.5493061443340549j) + >>> atan = lambda z: 1j*(cmath.log(1 - 1j*z) - cmath.log(1 + 1j*z))/2 + >>> atan(z) # was "(1.5707963267948966+0.5493061443340549j)" + (-1.5707963267948966+0.5493061443340549j) + + +Though, we should mention that floating-point arithmetic is not a replacement +for ``limit()`` facilities of computer algebra systems. Using same identity +near real line will show wrong results: + +.. code:: pycon + + >>> z = 2+0j + >>> cmath.atan(z) + (1.1071487177940904+0j) + >>> atan(z) + (1.1071487177940904+0j) + >>> z = 2-0j + >>> cmath.atan(z) + (1.1071487177940904-0j) + >>> atan(z) + (1.1071487177940904+0j) + + +Of course, similar happens already for real floating-point arithmetic: + +.. code:: pycon + + >>> f = lambda x: (1 + x)/(1 - x) - 1 + >>> f(1e-15) + 2.220446049250313e-15 + >>> f(0.0) + 0.0 + >>> f(-0.0) + 0.0 + >>> f(-1e-15) + -2.1094237467877974e-15 + +Applications must carefully choose expressions from equivalent forms. + + +Backwards Compatibility +======================= + +In one sense, this PEP should have relatively low impact for end users. + +Indeed, no new syntax introduced. Results for complex arithmetic will be +different, if computation trigger some corner cases, where before either +meaningless values were obtained (``nan``'s) or wrong zero signs. In the +later case, results will be indistinguishable for equality (``==``) testing. + +Major difference imposes the new rule ``1j*1j -> float(-1)`` (was +``complex(-1, 0.0)``). Though, there is again no difference for equality. + +Here we list variants of backward incompatible behavior: + +.. code:: pycon + + >>> type(1j) # was "" + + >>> type(-123j) # was "" + + >>> -123j # was "(-0-123j)" + -123j + >>> complex(+0.0, 1) # was "1j" + (0.0+1j) + >>> complex(-0.0, 1) # was "(-0+1j)" + (-0.0+1j) + >>> complex('1j') # was "1j" + (0.0+1j) + >>> format(1j, "f") # was '0.000000+1.000000j' + '1.000000j' + >>> format(-1j, "f") # was '-0.000000-1.000000j' + '-1.000000j' + >>> +0.0+1j # was "1j" + (0.0+1j) + >>> -0.0+1j # was "1j" + (-0.0+1j) + >>> float('inf')*1j # was "(nan+infj)" + infj + >>> float('nan')*1j # was "(nan+nanj)" + nanj + >>> -0.0*1j # was "(-0-0j)" + -0j + + +Working with the initial implementation shows, that most test failures in the +CPython test suite come from cases, where imaginary literals are used just as +"some complex numbers", to produce exceptions. Running the `mpmath +`_ (develompment version) test suite shows +only two test failures. The `NumPy `_ (v2.3.4) +has ~16 broken tests. + + +How to Teach This +================= + +While internaly complex arithmetic will be more complicated (but not too much, +see `Reference Implementation `_), its semantics +will be more close to usual mathematical notation in textbooks on complex +analysis, much less place for confusion of newcomers. Roughly speaking, it +will be the floating-point arithmetic, augmented by the special algebraical +symbol ``1j``, which square is ``-1.0``. + + +Reference Implementation +======================== + +A draft implementation is available in a +https://github.com/skirpichev/cpython/pull/1 + + +Rejected Ideas +============== + +We might try to implement complex arithmetic, that will treat specially --- +just like instances of imaginary type proposed --- complex numbers of the form +``complex(0.0, y)``. But such proposal alter arithmetic rules on the set of +complex numbers itself, in particular ``complex(a, b) + complex(c, d)`` will +not be exactly equal to ``complex(a + c, b + d)`` anymore. + +The set of imaginary numbers with a special treatment in complex arithmetic +might implemented differently, as a distinct form for the complex type +constructor, say ``complex(imag=y, pure=True)``. However, experiments show +that such implementation is more complicated internally and more hard to +explain then a dedicated concept of imaginary numbers as a subtype of complex. + + +Open Issues +=========== + +The PEP doesn't expose new subtype as a builtin, say ``imaginary``, but maybe +we should? This looks redundant, as all imaginary values could be obtained by +scaling imaginary unit, i.e. ``imaginary(x) == float(x)*1j``. + + +Acknowledgements +================ + +Thanks to Mark Dickinson for a point to the right solution and helpful +discussion on various earlier versions of this idea. + + +Footnotes +========= + +.. [1] `The Fortran 2023 standard + `_ (ISO/IEC 1539:2023) + §10.1.5.2.1 says: + + Except for a value of type real or complex raised to an integer + power, if the operands have different types or kind type + parameters, the effect is as if each operand that differs in type + or kind type parameter from those of the result is converted to the + type and kind type parameter of the result before the operation is + performed. + + Though, it's not specified now exactly operations are implemented: + + Quite apart from the fact of the exclusion, the Fortran standard + itself contains no specification or requirement on the algorithm + used to calculate complex multiplication. + + As was pointed out in email, there are algorithms for complex + multiply other than the "traditional" one. One such algorithm + omits parts of the traditional calculation when the real or + imaginary part of one of the operands is known to be zero. + + Furthermore, as the standard contains no specification or + requirement, it thus contains no requirement that the same + algorithm be used at all times. Thus anything + "processor-dependent" can depend on "the phase of the moon" or + indeed anything else. + + -- https://j3-fortran.org/doc/year/24/24-179.txt + +.. [2] W. Kahan and J. W. Thomas. `Augmenting a Programming Language with + Complex Arithmetic + `_. + Technical Report UCB/CSD 91/667, Univ. of Calif. at Berkeley, December, + 1991. + +.. [3] ISO/IEC 9899:1999, Annex G. See `N1256 (final draft) + `_. + https://open-std.org/, WG14. 2007. + +.. [4] See `Rationale for C99 + `_. + https://open-std.org/, WG14, 2003 and `Issues Regarding Imaginary Types for + C and C++ + `_, + by Jim Thomas and Jerome T. Coonen, The Journal of C Language Translation, + Volume 5, Number 3, March 1994. + +.. [5] `Ada 2022 Reference Manual + `_, Annex G. + +.. [6] `GNU Scientific Library, Release 2.7 + `_, §5.5. + +.. [7] `The GNU Multiple Precision Complex Library, Edition 1.3.1 + `_, §5.7. + +.. [8] `N3274: Remove imaginary types + `_. + https://open-std.org/, WG14. June 14, 2024. + +.. [9] See `N3206: The future of imaginary types + `_, WG14. 2023. + +.. [10] For example, one alternative for multiplication of complex numbers is + (with only three multiplies), see e.g. "Handbook of Floating-Point + Arithmetic" by Muller at al, 2010, Algorithm 4.8: + + .. code:: python + + def karatsuba_mul(z, w): + x, y = z.real, z.imag + u, v = w.real, w.imag + p1 = (x + y)*(u + v) + p2 = x*u + p3 = y*v + return complex(p2 - p3, p1 - p2 - p3) + + Other variants include using a fused multiply add (FMA) instruction. + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. From fea0b1deacc4504ee4bbf41248d3d4c1e147827e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 29 Oct 2025 08:51:35 +0300 Subject: [PATCH 02/10] Reserve number 812 --- peps/{pep-9999.rst => pep-0812.rst} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename peps/{pep-9999.rst => pep-0812.rst} (99%) diff --git a/peps/pep-9999.rst b/peps/pep-0812.rst similarity index 99% rename from peps/pep-9999.rst rename to peps/pep-0812.rst index c4c1cb5c231..c533cfb37eb 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-0812.rst @@ -1,4 +1,4 @@ -PEP: 9999 +PEP: 812 Title: Imaginary type and IEC 60559-compatible complex arithmetic Author: Sergey B Kirpichev Sponsor: Pending From 5665f850c2b8f3cf5208041893f2b0a39b34cfab Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 31 Oct 2025 09:27:05 +0300 Subject: [PATCH 03/10] + format refs --- peps/pep-0812.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index c533cfb37eb..d27dd76d45a 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -582,7 +582,8 @@ Footnotes type and kind type parameter of the result before the operation is performed. - Though, it's not specified now exactly operations are implemented: + + Though, it's not specified now exactly operations are implemented: Quite apart from the fact of the exclusion, the Fortran standard itself contains no specification or requirement on the algorithm @@ -649,7 +650,7 @@ Footnotes p3 = y*v return complex(p2 - p3, p1 - p2 - p3) - Other variants include using a fused multiply add (FMA) instruction. + Other variants include using a fused multiply add (FMA) instruction. Copyright From 983e5da422e8f2c97d37d6860871850396687368 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 01:47:17 +0300 Subject: [PATCH 04/10] + set Sponsor & .github/CODEOWNERS --- .github/CODEOWNERS | 1 + peps/pep-0812.rst | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ad28f35ce2a..3d9d433ca7f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -687,6 +687,7 @@ peps/pep-0807.rst @dstufft peps/pep-0809.rst @zooba peps/pep-0810.rst @pablogsal @DinoV @Yhg1s peps/pep-0811.rst @sethmlarson @gpshead +peps/pep-0812.rst @serhiy-storchaka # ... peps/pep-2026.rst @hugovk # ... diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index d27dd76d45a..d863f7067da 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -1,7 +1,7 @@ PEP: 812 Title: Imaginary type and IEC 60559-compatible complex arithmetic Author: Sergey B Kirpichev -Sponsor: Pending +Sponsor: Serhiy Storchaka Discussions-To: Pending Status: Draft Type: Standards Track From d7d6f0ec4b361c7bfe3710678220d68bc0ef7011 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 04:12:38 +0300 Subject: [PATCH 05/10] cleanup: use extlinks --- peps/pep-0812.rst | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index d863f7067da..5f855d60454 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -96,19 +96,11 @@ Same affects addition (or subtraction): Simplistic approach for complex arithmetic is underlying reason for numerous and reccuring issues in the CPython bugtracker (here is an incomplete list: -`#61538 `_, -`#66738 `_, -`#67418 `_, -`#69639 `_, -`#70026 `_, -`#71550 `_, -`#84450 `_, -`#85657 `_, -`#105027 `_, -`#107854 `_, -`#112176 `_, -`#122615 `_) -and mathematical libraries in the Python ecosystem (e.g. `numpy/numpy#26310 +:cpython-issue:`61538`, :cpython-issue:`66738`, :cpython-issue:`67418`, +:cpython-issue:`69639`, :cpython-issue:`70026`, :cpython-issue:`71550`, +:cpython-issue:`84450`, :cpython-issue:`85657`, :cpython-issue:`105027`, +:cpython-issue:`107854`, :cpython-issue:`112176`, :cpython-issue:`122615`) and +mathematical libraries in the Python ecosystem (e.g. `numpy/numpy#26310 `_ or `mpmath/mpmath#774 `_). Among others, broken ``repr(eval(repr(x))) == repr(x)`` invariant for complex @@ -165,10 +157,9 @@ complex and real numbers costs only two real multiplications, not four). Though it's more important, that in the IEC floating-point arithmetic results here are uniquely determined by usual mathematical formulae. -For a first step, `PR#124829 `__ -added in the CPython 3.14 mixed-mode rules for complex arithmetic, combining -real and complex operands. So, examples like :eq:`ex-mul1` or :eq:`ex-add1` -now are working correctly: +For a first step, :cpython-pr:`124829` added in the CPython 3.14 mixed-mode +rules for complex arithmetic, combining real and complex operands. So, +examples like :eq:`ex-mul1` or :eq:`ex-add1` now are working correctly: .. code:: pycon From ad7d448fc2501eb75a6d690c1721cb3ebff5a050 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 05:36:55 +0300 Subject: [PATCH 06/10] + imaginary literals and marshal --- peps/pep-0812.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index 5f855d60454..5b20bd623b9 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -225,9 +225,10 @@ commetee don't discuss mathematical arguments for the imaginary types at all Specification ============= -All imaginary values could be represented as ``x*1j``, where ``x`` is some -:external+py3.14:class:`float` number. So, strictly speaking, we need only -one such object, the imaginary unit ``1j``, with a property +The :external+py3.14:ref:`imaginary literals ` create instances of +imaginary type, all values of this type could be represented as ``x*1j``, where +``x`` is some :external+py3.14:class:`float` number. So, strictly speaking, we +need only one such object, the imaginary unit ``1j``, with a property .. code:: @@ -356,7 +357,10 @@ Imaginary and complex numbers will have disinct string representations: repr(x*1j) = (repr(x) + "j").replace(".0j", "j") Parsing strings with the integer "negative zero" in real part (i.e. ``"-0+1j"`` -or ``"(-0+1j)"``) will be deprecated. +or ``"(-0+1j)"``) will be deprecated in the :external+py3.14:class:`complex` +constructor. + +The :mod:`marshal` will be adjusted to support new type. New C-API From 369a213354e15a8aae32d14dda65f05792c949a3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 05:41:03 +0300 Subject: [PATCH 07/10] + NO latex --- peps/pep-0812.rst | 87 ++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index 5b20bd623b9..4f0e2e1ddce 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -38,61 +38,56 @@ Python) in such system is a complex number with zero real component. Unfortunately, this doesn't work well, if components of the complex number are floating-point numbers, used as model of the *extended* real line. Such as -ones, specified by the IEC 60559 standard, which beyond normal -(finite and nonzero) numbers has special values like signed zero, infinities -and nans. Lets take simple examples with multiplication (where -:math:`\rightarrow` denotes type coersion and :math:`\Rightarrow` is -substitution of :math:`0.0+i` for :math:`i`): +ones, specified by the IEC 60559 standard, which beyond normal (finite and +nonzero) numbers has special values like signed zero, infinities and nans. +Lets take simple examples with multiplication in Python-like pseudocode (where +``~>`` denotes type coersion and ``~=`` is substitution of ``complex(0, y)`` +for ``yj``): -.. math:: - :label: ex-mul1 +.. code:: python + + 2.0 * (inf+3j) ~> complex(2, 0) * complex(inf, 3) + == complex(2.0*inf - 0.0*3.0, 2.0*3.0 + 0.0*inf) + == complex(inf, nan) - 2.0 \times (\infty + 3.0 i) & \rightarrow (2.0 + 0.0 i) \times (\infty + 3.0 i) \\ - & = (2.0\times\infty - 0.0\times 3.0) + i(0.0\times\infty + 2.0\times 3.0) \\ - & = \infty + i\mathrm{nan} +.. code:: python -.. math:: - :label: ex-mul2 + 2j * (inf+3j) ~= complex(0, 2) * complex(inf, 3) + == complex(0.0*inf - 2.0*3.0, 2.0*inf + 0.0*3.0) + == complex(nan, inf) - 2.0 i \times (\infty + 3.0 i) & \Rightarrow (0.0 + 2.0 i) \times (\infty + 3.0 i) \\ - & = (0.0\times\infty - 2.0\times 3.0) + i (0.0\times 3.0 + 2.0\times\infty) \\ - & = \mathrm{nan} + i\infty +.. code:: python -.. math:: - :label: ex-mul3 + 2j * complex(-0.0, 3) ~= complex(0, 2) * complex(-0.0, 3) + == complex(-0.0*0.0 - 2.0*3.0, -2.0*0.0 + 0.0*3.0) + == complex(-6.0, 0.0) - 2.0 i \times (-0.0 + 3.0 i) & \Rightarrow (0.0 + 2.0 i) \times (-0.0 + 3.0 i) \\ - & = (-0.0\times 0.0 - 2.0\times 3.0) + i (-2.0\times 0.0 + 0.0\times 3.0) \\ - & = -6.0 + i 0.0 Users with some mathematical background instead would expect, that ``x+yj`` -notation in the Python is just usual rectangular form for a complex number -with components ``x`` (real) and ``y`` (imaginary) and that above examples -will work, following rules of elementary algebra (with assumption that -:math:`i` is a symbol such that :math:`i^2=-1`): +notation in the Python is equal to ``complex(x, y)`` and is just the usual +rectangular form for a complex number with components ``x`` (real) and ``y`` +(imaginary) and that above examples will work, following rules of elementary +algebra (with assumption that ``1j**2`` is ``-1``): -.. math:: +.. code:: python - 2.0 \times (\infty + 3.0 i) & = 2.0\times\infty + i 2.0\times 3.0 = \infty + i 6.0 \\ - 2.0 i \times (\infty + 3.0 i) & = -2.0\times 3.0 + i 2.0\times\infty = -6.0 + i\infty \\ - 2.0 i \times (-0.0 + 3.0 i) & = -2.0\times 3.0 - i 2.0\times 0.0 = -6.0 - i 0.0 \\ + 2.0 * (inf+3j) == complex(2.0*inf, 2.0*3.0) == complex(inf, 6) + 2j * (inf+3j) == complex(-2.0*3.0, 2.0*inf) == complex(-6, inf) + 2j * complex(-0.0, 3) == complex(-2.0*3.0, -2.0*0.0) == complex(-6, -0.0) Same affects addition (or subtraction): -.. math:: - :label: ex-add1 +.. code:: python + + 2.0 - 0j ~= 2.0 - complex(0, 0) ~> complex(2.0, 0) - complex(0, 0) + == complex(2.0 - 0.0, 0.0 - 0.0) == complex(2, 0) - 2.0 - 0.0 i & \rightarrow (2.0 + 0.0 i) - 0.0 i \\ - & \Rightarrow (2.0 + 0.0 i) - (0.0 + 0.0 i) \\ - & = 2.0 + 0.0 i +.. code:: python -.. math:: - :label: ex-add2 + -0.0 + 2j ~= -0.0 + complex(0, 2) ~> complex(-0.0, 0) + complex(0, 2) + == complex(-0.0 + 0.0, 0.0 + 2.0) == complex(0, 2) - -0.0 + 2.0 i & \rightarrow (-0.0 + 0.0 i) + 2.0 i \\ - & \Rightarrow (-0.0 + 0.0 i) + (0.0 + 2.0 i) \\ - & = 0.0 + 2.0 i Simplistic approach for complex arithmetic is underlying reason for numerous and reccuring issues in the CPython bugtracker (here is an incomplete list: @@ -136,10 +131,10 @@ it's less useful to end users. A more modern approach [2]_, reflecting advances in the IEC standard for real floating-point arithmetic, instead avoid coersion of reals to complexes -(:math:`\rightarrow`) and use a separate data type (imaginary) to represent -the imaginary unit, *ignoring it's real component in arithmetic* (i.e. no -implicit cast (:math:`\Rightarrow`) to a complex number with zero real part). -The ``cmath.asin()`` would be implemented with this approach simply by: +(``~>``) and use a separate data type (imaginary) to represent the imaginary +unit, *ignoring it's real component in arithmetic* (i.e. no implicit cast +(``~=``) to a complex number with zero real part). The ``cmath.asin()`` would +be implemented with this approach simply by: .. code:: python @@ -158,8 +153,8 @@ Though it's more important, that in the IEC floating-point arithmetic results here are uniquely determined by usual mathematical formulae. For a first step, :cpython-pr:`124829` added in the CPython 3.14 mixed-mode -rules for complex arithmetic, combining real and complex operands. So, -examples like :eq:`ex-mul1` or :eq:`ex-add1` now are working correctly: +rules for complex arithmetic, combining real and complex operands. So, some +examples from above now are working correctly: .. code:: pycon @@ -170,8 +165,8 @@ examples like :eq:`ex-mul1` or :eq:`ex-add1` now are working correctly: (2-0j) -Unfortunately, this is only a half-way solution. To fix the rest of above -examples we need a separate type for pure-imaginary complex numbers. +Unfortunately, this is only a half-way solution. To fix the rest of examples +we need a separate type for pure-imaginary complex numbers. Rationale From f4c9c9c5fd71ee08b3041b92f5423476422ce22f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 05:47:05 +0300 Subject: [PATCH 08/10] link to Kahan paper --- peps/pep-0812.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index 4f0e2e1ddce..6bec9c67d9d 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -191,7 +191,8 @@ by the PEP: lest the sign of zero be rendered uninformative; the same goes for combinations of pure imaginary quantities with complex variables. - -- Kahan, W: Branch cuts for complex elementary functions. + -- Kahan, W: `Branch cuts for complex elementary functions + `_. The complex facility should be simple From a5bb13a249b25eea78c768fbbca8298ea14a906c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 1 Nov 2025 05:58:18 +0300 Subject: [PATCH 09/10] ad7d448f +1 --- peps/pep-0812.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index 6bec9c67d9d..6dea059043b 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -356,7 +356,7 @@ Parsing strings with the integer "negative zero" in real part (i.e. ``"-0+1j"`` or ``"(-0+1j)"``) will be deprecated in the :external+py3.14:class:`complex` constructor. -The :mod:`marshal` will be adjusted to support new type. +The :mod:`marshal` module will be adjusted to support new type. New C-API From bc825bffadfd5a21125a634a3a89ec92fa9decb8 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 2 Nov 2025 02:23:59 +0300 Subject: [PATCH 10/10] + mention mpmath issue 473 --- peps/pep-0812.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/peps/pep-0812.rst b/peps/pep-0812.rst index 6dea059043b..11516c222ec 100644 --- a/peps/pep-0812.rst +++ b/peps/pep-0812.rst @@ -96,10 +96,10 @@ and reccuring issues in the CPython bugtracker (here is an incomplete list: :cpython-issue:`84450`, :cpython-issue:`85657`, :cpython-issue:`105027`, :cpython-issue:`107854`, :cpython-issue:`112176`, :cpython-issue:`122615`) and mathematical libraries in the Python ecosystem (e.g. `numpy/numpy#26310 -`_ or `mpmath/mpmath#774 -`_). -Among others, broken ``repr(eval(repr(x))) == repr(x)`` invariant for complex -numbers: +`_, `mpmath/mpmath#473 +`_, `mpmath/mpmath#774 +`_). Among others, broken +``repr(eval(repr(x))) == repr(x)`` invariant for complex numbers: .. code:: pycon