Skip to content

Commit 413dc1d

Browse files
committed
Imaginary type and IEC 60559-compatible complex arithmetic
"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." (c) Kahan, W: Branch cuts for complex elementary functions. That's why C standards since C99 introduce imaginary types. This patch implements similar extension to the Python language. Lets consider (actually interrelated) problems, which will be solved on this way. 1) Now complex arithmetic could be used for implementation of mathematical functions without special "corner cases", with textbooks formulae. Take the inverse tangent as an example: >>> z = 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) # real part had wrong sign before (-1.5707963267948966+0.5493061443340549j) 2) Previously, we have unsigned imaginary literals with the following semantics: a±bj = complex(a ± 0.0, ±b) complex(a, ±b)*cj = complex(a*0.0 ∓ b*c, a*c ± b*0.0) While this behaviour was well documented, most users would expect instead here: a±bj = complex(a, ±b) complex(a, ±b)*cj = complex(∓b*c, a*c) i.e. that it follows to the rectangular notation for complex numbers. For example: >>> -0.0+1j # was 1j (-0.0+1j) >>> float('inf')*1j # was (nan+infj) infj >>> -0.0+1j # was 1j (-0.0+1j) >>> complex(-0.0, 1) # was (-0+1j), not funny signed integer zero (-0.0+1j) 3) The ``eval(repr(x)) == x`` invariant now holds for the complex type. What's changed: * Added a new subtype (imaginary) of the complex type with few overloaded methods (conjugate() and __getnewargs__()). * Complex and imaginary types implement IEC 60559-compatible complex arithmetic (as specified by C11 Annex G). * Imaginary literals now produce instances of imaginary type. * cmath.infj/nanj were changed to be of imaginary type. * Modules ast, code, copy, marshal got support for imaginary type. * Few tests adapted to use complex, instead of imaginary literals * Print dot for signed zeros in the real part of repr(complex) * repr(complex) now prints real part even if it's zero.
1 parent 3000594 commit 413dc1d

38 files changed

+961
-108
lines changed

Doc/c-api/complex.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ pointers. This is consistent throughout the API.
4747
This function is :term:`soft deprecated`.
4848
4949
50+
.. c:function:: Py_complex _Py_ci_sum(Py_complex left, double right)
51+
52+
Return the sum of a complex number and an imaginary number, using the C
53+
:c:type:`Py_complex` representation.
54+
55+
.. versionadded:: next
56+
57+
5058
.. c:function:: Py_complex _Py_c_diff(Py_complex left, Py_complex right)
5159
5260
Return the difference between two complex numbers, using the C
@@ -56,6 +64,22 @@ pointers. This is consistent throughout the API.
5664
This function is :term:`soft deprecated`.
5765
5866
67+
.. c:function:: Py_complex _Py_ci_diff(Py_complex left, double right)
68+
69+
Return the difference between a complex number and an imaginary number,
70+
using the C :c:type:`Py_complex` representation.
71+
72+
.. versionadded:: next
73+
74+
75+
.. c:function:: Py_complex _Py_ic_diff(double left, Py_complex right)
76+
77+
Return the difference between an imaginary number and a complex number,
78+
using the C :c:type:`Py_complex` representation.
79+
80+
.. versionadded:: next
81+
82+
5983
.. c:function:: Py_complex _Py_c_neg(Py_complex num)
6084
6185
Return the negation of the complex number *num*, using the C
@@ -74,6 +98,14 @@ pointers. This is consistent throughout the API.
7498
This function is :term:`soft deprecated`.
7599
76100
101+
.. c:function:: Py_complex _Py_ci_prod(Py_complex left, double right)
102+
103+
Return the product of a complex number and an imaginary number, using the C
104+
:c:type:`Py_complex` representation.
105+
106+
.. versionadded:: next
107+
108+
77109
.. c:function:: Py_complex _Py_c_quot(Py_complex dividend, Py_complex divisor)
78110
79111
Return the quotient of two complex numbers, using the C :c:type:`Py_complex`
@@ -86,6 +118,28 @@ pointers. This is consistent throughout the API.
86118
This function is :term:`soft deprecated`.
87119
88120
121+
.. c:function:: Py_complex _Py_ci_quot(Py_complex dividend, double divisor)
122+
123+
Return the quotient of a complex number and an imaginary number, using the C
124+
:c:type:`Py_complex` representation.
125+
126+
If *divisor* is zero, this method returns zero and sets
127+
:c:data:`errno` to :c:macro:`!EDOM`.
128+
129+
.. versionadded:: next
130+
131+
132+
.. c:function:: Py_complex _Py_ic_quot(double dividend, Py_complex divisor)
133+
134+
Return the quotient of an imaginary number and a complex number, using the C
135+
:c:type:`Py_complex` representation.
136+
137+
If *divisor* is zero, this method returns zero and sets
138+
:c:data:`errno` to :c:macro:`!EDOM`.
139+
140+
.. versionadded:: next
141+
142+
89143
.. c:function:: Py_complex _Py_c_pow(Py_complex num, Py_complex exp)
90144
91145
Return the exponentiation of *num* by *exp*, using the C :c:type:`Py_complex`

Doc/data/stable_abi.dat

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/library/cmath.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ Constants
330330
.. data:: infj
331331

332332
Complex number with zero real part and positive infinity imaginary
333-
part. Equivalent to ``complex(0.0, float('inf'))``.
333+
part. Equivalent to ``float('inf')*1j``.
334334

335335
.. versionadded:: 3.6
336336

@@ -346,7 +346,7 @@ Constants
346346
.. data:: nanj
347347

348348
Complex number with zero real part and NaN imaginary part. Equivalent to
349-
``complex(0.0, float('nan'))``.
349+
``float('nan')*1j``.
350350

351351
.. versionadded:: 3.6
352352

Doc/library/functions.rst

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ are always available. They are listed here in alphabetical order.
3333
| | :func:`complex` | | | | **P** | | **V** |
3434
| | | | **I** | | :func:`pow` | | :func:`vars` |
3535
| | **D** | | :func:`id` | | :func:`print` | | |
36-
| | :func:`delattr` | | :func:`input` | | :func:`property` | | **Z** |
37-
| | |func-dict|_ | | :func:`int` | | | | :func:`zip` |
38-
| | :func:`dir` | | :func:`isinstance` | | | | |
39-
| | :func:`divmod` | | :func:`issubclass` | | | | **_** |
36+
| | :func:`delattr` | | :func:`imaginary` | | :func:`property` | | |
37+
| | |func-dict|_ | | :func:`input` | | | | **Z** |
38+
| | :func:`dir` | | :func:`int` | | | | :func:`zip` |
39+
| | :func:`divmod` | | :func:`isinstance` | | | | |
40+
| | | | :func:`issubclass` | | | | **_** |
4041
| | | | :func:`iter` | | | | :func:`__import__` |
4142
+-------------------------+-----------------------+-----------------------+-------------------------+
4243

@@ -388,7 +389,7 @@ are always available. They are listed here in alphabetical order.
388389
>>> complex('+1.23')
389390
(1.23+0j)
390391
>>> complex('-4.5j')
391-
-4.5j
392+
(0.0-4.5j)
392393
>>> complex('-1.23+4.5j')
393394
(-1.23+4.5j)
394395
>>> complex('\t( -1.23+4.5J )\n')
@@ -398,7 +399,7 @@ are always available. They are listed here in alphabetical order.
398399
>>> complex(1.23)
399400
(1.23+0j)
400401
>>> complex(imag=-4.5)
401-
-4.5j
402+
(0.0-4.5j)
402403
>>> complex(-1.23, 4.5)
403404
(-1.23+4.5j)
404405

@@ -442,7 +443,7 @@ are always available. They are listed here in alphabetical order.
442443

443444
See also :meth:`complex.from_number` which only accepts a single numeric argument.
444445

445-
If all arguments are omitted, returns ``0j``.
446+
If all arguments are omitted, returns ``0.0+0j``.
446447

447448
The complex type is described in :ref:`typesnumeric`.
448449

@@ -970,6 +971,14 @@ are always available. They are listed here in alphabetical order.
970971
.. audit-event:: builtins.id id id
971972

972973

974+
.. class:: imaginary(x=0.0)
975+
976+
Return an imaginary number with the value ``float(x)*1j``. If argument is
977+
omitted, returns ``0j``.
978+
979+
.. versionadded:: next
980+
981+
973982
.. function:: input()
974983
input(prompt)
975984

Doc/reference/lexical_analysis.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,8 +1297,8 @@ Imaginary literals
12971297

12981298
Python has :ref:`complex number <typesnumeric>` objects, but no complex
12991299
literals.
1300-
Instead, *imaginary literals* denote complex numbers with a zero
1301-
real part.
1300+
Instead, *imaginary literals* denote complex numbers without a real part, an instance
1301+
of :class:`imaginary`.
13021302

13031303
For example, in math, the complex number 3+4.2\ *i* is written
13041304
as the real number 3 added to the imaginary number 4.2\ *i*.

Include/complexobject.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,21 @@ extern "C" {
99
/* Complex object interface */
1010

1111
PyAPI_DATA(PyTypeObject) PyComplex_Type;
12+
PyAPI_DATA(PyTypeObject) PyImaginary_Type;
1213

1314
#define PyComplex_Check(op) PyObject_TypeCheck((op), &PyComplex_Type)
1415
#define PyComplex_CheckExact(op) Py_IS_TYPE((op), &PyComplex_Type)
1516

17+
#define PyImaginary_Check(op) PyObject_TypeCheck((op), &PyImaginary_Type)
18+
#define PyImaginary_CheckExact(op) Py_IS_TYPE((op), &PyImaginary_Type)
19+
1620
PyAPI_FUNC(PyObject *) PyComplex_FromDoubles(double real, double imag);
1721

1822
PyAPI_FUNC(double) PyComplex_RealAsDouble(PyObject *op);
1923
PyAPI_FUNC(double) PyComplex_ImagAsDouble(PyObject *op);
2024

25+
PyAPI_FUNC(PyObject *) PyImaginary_FromDouble(double imag);
26+
2127
#ifndef Py_LIMITED_API
2228
# define Py_CPYTHON_COMPLEXOBJECT_H
2329
# include "cpython/complexobject.h"

Include/cpython/complexobject.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@ typedef struct {
1010
/* Operations on complex numbers (soft deprecated
1111
since Python 3.15). */
1212
PyAPI_FUNC(Py_complex) _Py_c_sum(Py_complex, Py_complex);
13+
PyAPI_FUNC(Py_complex) _Py_cr_sum(Py_complex, double);
14+
PyAPI_FUNC(Py_complex) _Py_ci_sum(Py_complex, double);
1315
PyAPI_FUNC(Py_complex) _Py_c_diff(Py_complex, Py_complex);
16+
PyAPI_FUNC(Py_complex) _Py_cr_diff(Py_complex, double);
17+
PyAPI_FUNC(Py_complex) _Py_rc_diff(double, Py_complex);
18+
PyAPI_FUNC(Py_complex) _Py_ci_diff(Py_complex, double);
19+
PyAPI_FUNC(Py_complex) _Py_ic_diff(double, Py_complex);
1420
PyAPI_FUNC(Py_complex) _Py_c_neg(Py_complex);
1521
PyAPI_FUNC(Py_complex) _Py_c_prod(Py_complex, Py_complex);
22+
PyAPI_FUNC(Py_complex) _Py_cr_prod(Py_complex, double);
23+
PyAPI_FUNC(Py_complex) _Py_ci_prod(Py_complex, double);
1624
PyAPI_FUNC(Py_complex) _Py_c_quot(Py_complex, Py_complex);
25+
PyAPI_FUNC(Py_complex) _Py_cr_quot(Py_complex, double);
26+
PyAPI_FUNC(Py_complex) _Py_rc_quot(double, Py_complex);
27+
PyAPI_FUNC(Py_complex) _Py_ci_quot(Py_complex, double);
28+
PyAPI_FUNC(Py_complex) _Py_ic_quot(double, Py_complex);
1729
PyAPI_FUNC(Py_complex) _Py_c_pow(Py_complex, Py_complex);
1830
PyAPI_FUNC(double) _Py_c_abs(Py_complex);
1931

Lib/ast.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def _convert_literal(node):
8989
isinstance(node, UnaryOp)
9090
and isinstance(node.op, (UAdd, USub))
9191
and isinstance(node.operand, Constant)
92-
and type(operand := node.operand.value) in (int, float, complex)
92+
and type(operand := node.operand.value) in (int, float, imaginary)
9393
):
9494
if isinstance(node.op, UAdd):
9595
return + operand
@@ -101,7 +101,7 @@ def _convert_literal(node):
101101
and isinstance(node.left, (Constant, UnaryOp))
102102
and isinstance(node.right, Constant)
103103
and type(left := _convert_literal(node.left)) in (int, float)
104-
and type(right := _convert_literal(node.right)) is complex
104+
and type(right := _convert_literal(node.right)) is imaginary
105105
):
106106
if isinstance(node.op, Add):
107107
return left + right

Lib/copy.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def copy(x):
101101

102102

103103
_copy_atomic_types = {types.NoneType, int, float, bool, complex, str, tuple,
104-
bytes, frozenset, type, range, slice, property,
104+
imaginary, bytes, frozenset, type, range, slice, property,
105105
types.BuiltinFunctionType, types.EllipsisType,
106106
types.NotImplementedType, types.FunctionType, types.CodeType,
107107
weakref.ref, super}
@@ -164,7 +164,8 @@ def deepcopy(x, memo=None, _nil=[]):
164164

165165
_atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType,
166166
int, float, bool, complex, bytes, str, types.CodeType, type, range,
167-
types.BuiltinFunctionType, types.FunctionType, weakref.ref, property}
167+
types.BuiltinFunctionType, types.FunctionType, weakref.ref,
168+
property, imaginary}
168169

169170
_deepcopy_dispatch = d = {}
170171

Lib/test/test_bytes.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -785,12 +785,12 @@ def __int__(self):
785785
('%X format: an integer is required, not float', b'%X', 2.11),
786786
('%o format: an integer is required, not float', b'%o', 1.79),
787787
('%x format: an integer is required, not PseudoFloat', b'%x', pi),
788-
('%x format: an integer is required, not complex', b'%x', 3j),
789-
('%X format: an integer is required, not complex', b'%X', 2j),
790-
('%o format: an integer is required, not complex', b'%o', 1j),
791-
('%u format: a real number is required, not complex', b'%u', 3j),
792-
('%i format: a real number is required, not complex', b'%i', 2j),
793-
('%d format: a real number is required, not complex', b'%d', 2j),
788+
('%x format: an integer is required, not complex', b'%x', 1+3j),
789+
('%X format: an integer is required, not complex', b'%X', 1+2j),
790+
('%o format: an integer is required, not complex', b'%o', 1+1j),
791+
('%u format: a real number is required, not complex', b'%u', 1+3j),
792+
('%i format: a real number is required, not complex', b'%i', 1+2j),
793+
('%d format: a real number is required, not complex', b'%d', 1+2j),
794794
(
795795
r'%c requires an integer in range\(256\)'
796796
r' or a single byte, not .*\.PseudoFloat',

0 commit comments

Comments
 (0)