Skip to content

Commit d062169

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 proposes similar extension to the Python language: * Added a new subtype (imaginary) of the complex type. New type has 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 - Lib/test/test_fractions.py - Lib/test/test_socket.py - Lib/test/test_str.py * Print dot for signed zeros in the real part of repr(complex): >>> complex(-0.0, 1) # was (-0+1j) (-0.0+1j) * repr(complex) naw prints real part even if it's zero. Lets consider (actually interrelated) problems, shown for unpatched code, which will be solved on this way. 1) New code allows to use complex arithmetic for implementation of mathematical functions without special "corner cases". Take the inverse tangent as an example: >>> z = complex(-0.0, 2) >>> cmath.atan(z) (-1.5707963267948966+0.5493061443340549j) >>> # real part was wrong: >>> 1j*(cmath.log(1 - 1j*z) - cmath.log(1 + 1j*z))/2 (1.5707963267948966+0.5493061443340549j) 2) Previously, we have only unsigned imaginary literals with the following semantics: ±a±bj = complex(±float(a), 0.0) ± complex(0.0, float(b)) While this behaviour was well documented, most users would expect instead here: ±a±bj = complex(±float(a), ±float(b)) i.e. that it follows to the rectangular notation for complex numbers. For example: >>> -0.0+1j # now (-0.0+1j) 1j >>> float('inf')*1j # now infj (nan+infj) 3) The ``eval(repr(x)) == x`` invariant was broken for the complex type: >>> complex(-0.0, 1.0) # also note funny signed integer zero below (-0+1j) >>> -0+1j 1j >>> complex(0.0, -cmath.inf) -infj >>> -cmath.infj (-0-infj)
1 parent 71ede11 commit d062169

31 files changed

+743
-100
lines changed

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
@@ -279,7 +279,7 @@ Constants
279279
.. data:: infj
280280

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

284284
.. versionadded:: 3.6
285285

@@ -295,7 +295,7 @@ Constants
295295
.. data:: nanj
296296

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

300300
.. versionadded:: 3.6
301301

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: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -979,11 +979,12 @@ Imaginary literals are described by the following lexical definitions:
979979
.. productionlist:: python-grammar
980980
imagnumber: (`floatnumber` | `digitpart`) ("j" | "J")
981981

982-
An imaginary literal yields a complex number with a real part of 0.0. Complex
983-
numbers are represented as a pair of floating-point numbers and have the same
984-
restrictions on their range. To create a complex number with a nonzero real
985-
part, add a floating-point number to it, e.g., ``(3+4j)``. Some examples of
986-
imaginary literals::
982+
An imaginary literal yields a complex number without a real part, an instance
983+
of :class:`imaginary`. Complex numbers are represented as a pair of
984+
floating-point numbers and have the same restrictions on their range. To
985+
create a complex number with a nonzero real part, add an integer or
986+
floating-point number to imaginary, e.g., ``3+4j``. Some examples of imaginary
987+
literals::
987988

988989
3.14j 10.j 10j .001j 1e100j 3.14e-10j 3.14_15_93j
989990

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"

Lib/ast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def _raise_malformed_node(node):
7373
msg += f' on line {lno}'
7474
raise ValueError(msg + f': {node!r}')
7575
def _convert_num(node):
76-
if not isinstance(node, Constant) or type(node.value) not in (int, float, complex):
76+
if not isinstance(node, Constant) or type(node.value) not in (int, float, imaginary, complex):
7777
_raise_malformed_node(node)
7878
return node.value
7979
def _convert_signed_num(node):

Lib/copy.py

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

103103
def _copy_immutable(x):
104104
return x
105-
for t in (types.NoneType, int, float, bool, complex, str, tuple,
105+
for t in (types.NoneType, int, float, bool, complex, imaginary, str, tuple,
106106
bytes, frozenset, type, range, slice, property,
107107
types.BuiltinFunctionType, types.EllipsisType,
108108
types.NotImplementedType, types.FunctionType, types.CodeType,
@@ -173,7 +173,8 @@ def deepcopy(x, memo=None, _nil=[]):
173173

174174
_atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType,
175175
int, float, bool, complex, bytes, str, types.CodeType, type, range,
176-
types.BuiltinFunctionType, types.FunctionType, weakref.ref, property}
176+
types.BuiltinFunctionType, types.FunctionType, weakref.ref,
177+
property, imaginary}
177178

178179
_deepcopy_dispatch = d = {}
179180

Lib/test/test_capi/test_complex.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class BadComplex3:
2323
def __complex__(self):
2424
raise RuntimeError
2525

26+
class ImaginarySubclass(imaginary):
27+
pass
28+
2629

2730
class CAPIComplexTest(ComplexesAreIdenticalMixin, unittest.TestCase):
2831
def test_check(self):
@@ -176,7 +179,7 @@ def test_py_cr_sum(self):
176179
# Test _Py_cr_sum()
177180
_py_cr_sum = _testcapi._py_cr_sum
178181

179-
self.assertComplexesAreIdentical(_py_cr_sum(-0j, -0.0)[0],
182+
self.assertComplexesAreIdentical(_py_cr_sum(-0.0 - 0j, -0.0)[0],
180183
complex(-0.0, -0.0))
181184

182185
def test_py_c_diff(self):
@@ -189,7 +192,7 @@ def test_py_cr_diff(self):
189192
# Test _Py_cr_diff()
190193
_py_cr_diff = _testcapi._py_cr_diff
191194

192-
self.assertComplexesAreIdentical(_py_cr_diff(-0j, 0.0)[0],
195+
self.assertComplexesAreIdentical(_py_cr_diff(-0.0 - 0j, 0.0)[0],
193196
complex(-0.0, -0.0))
194197

195198
def test_py_rc_diff(self):
@@ -275,7 +278,6 @@ def test_py_c_pow(self):
275278
self.assertEqual(_py_c_pow(max_num, 2),
276279
(complex(INF, INF), errno.ERANGE))
277280

278-
279281
def test_py_c_abs(self):
280282
# Test _Py_c_abs()
281283
_py_c_abs = _testcapi._py_c_abs
@@ -294,5 +296,41 @@ def test_py_c_abs(self):
294296
self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE)
295297

296298

299+
class CAPIImaginaryTest(unittest.TestCase):
300+
def test_check(self):
301+
# Test PyImaginary_Check()
302+
check = _testlimitedcapi.imaginary_check
303+
304+
self.assertTrue(check(2j))
305+
self.assertTrue(check(ImaginarySubclass(2)))
306+
self.assertFalse(check(ComplexSubclass(1+2j)))
307+
self.assertFalse(check(Complex()))
308+
self.assertFalse(check(3))
309+
self.assertFalse(check(3.0))
310+
self.assertFalse(check(object()))
311+
312+
# CRASHES check(NULL)
313+
314+
def test_checkexact(self):
315+
# PyImaginary_CheckExact()
316+
checkexact = _testlimitedcapi.imaginary_checkexact
317+
318+
self.assertTrue(checkexact(2j))
319+
self.assertFalse(checkexact(ImaginarySubclass(2)))
320+
self.assertFalse(checkexact(ComplexSubclass(1+2j)))
321+
self.assertFalse(checkexact(Complex()))
322+
self.assertFalse(checkexact(3))
323+
self.assertFalse(checkexact(3.0))
324+
self.assertFalse(checkexact(object()))
325+
326+
# CRASHES checkexact(NULL)
327+
328+
def test_fromdouble(self):
329+
# Test PyImaginary_FromDouble()
330+
fromdouble = _testlimitedcapi.imaginary_fromdouble
331+
332+
self.assertEqual(fromdouble(2.0), 2.0j)
333+
334+
297335
if __name__ == "__main__":
298336
unittest.main()

0 commit comments

Comments
 (0)