Skip to content

Commit 3980363

Browse files
committed
pythongh-69639: add mixed-mode rules for complex arithmetic (C-like)
"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. This patch implements mixed-mode arithmetic rules, combining real and complex variables as specified by C standards since C99 (in particular, there is no special version for the true division with real lhs operand). Most C compilers implementing C99+ Annex G have only these special rules (without support for imaginary type, which is going to be deprecated in C2y).
1 parent 133e929 commit 3980363

File tree

8 files changed

+167
-55
lines changed

8 files changed

+167
-55
lines changed

Doc/library/cmath.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ the function is then applied to the result of the conversion.
2424
imaginary axis we look at the sign of the real part.
2525

2626
For example, the :func:`cmath.sqrt` function has a branch cut along the
27-
negative real axis. An argument of ``complex(-2.0, -0.0)`` is treated as
27+
negative real axis. An argument of ``-2-0j`` is treated as
2828
though it lies *below* the branch cut, and so gives a result on the negative
2929
imaginary axis::
3030

31-
>>> cmath.sqrt(complex(-2.0, -0.0))
31+
>>> cmath.sqrt(-2-0j)
3232
-1.4142135623730951j
3333

34-
But an argument of ``complex(-2.0, 0.0)`` is treated as though it lies above
34+
But an argument of ``-2+0j`` is treated as though it lies above
3535
the branch cut::
3636

37-
>>> cmath.sqrt(complex(-2.0, 0.0))
37+
>>> cmath.sqrt(-2+0j)
3838
1.4142135623730951j
3939

4040

@@ -63,9 +63,9 @@ rectangular coordinates to polar coordinates and back.
6363
along the negative real axis. The sign of the result is the same as the
6464
sign of ``x.imag``, even when ``x.imag`` is zero::
6565

66-
>>> phase(complex(-1.0, 0.0))
66+
>>> phase(-1+0j)
6767
3.141592653589793
68-
>>> phase(complex(-1.0, -0.0))
68+
>>> phase(-1-0j)
6969
-3.141592653589793
7070

7171

Doc/library/stdtypes.rst

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ numeric literal yields an imaginary number (a complex number with a zero real
243243
part) which you can add to an integer or float to get a complex number with real
244244
and imaginary parts.
245245

246+
The constructors :func:`int`, :func:`float`, and
247+
:func:`complex` can be used to produce numbers of a specific type.
248+
246249
.. index::
247250
single: arithmetic
248251
pair: built-in function; int
@@ -262,12 +265,15 @@ and imaginary parts.
262265

263266
Python fully supports mixed arithmetic: when a binary arithmetic operator has
264267
operands of different numeric types, the operand with the "narrower" type is
265-
widened to that of the other, where integer is narrower than floating point,
266-
which is narrower than complex. A comparison between numbers of different types
267-
behaves as though the exact values of those numbers were being compared. [2]_
268+
widened to that of the other, where integer is narrower than floating point.
269+
Arithmetic with complex and real operands is defined by the usual mathematical
270+
formula, for example::
268271

269-
The constructors :func:`int`, :func:`float`, and
270-
:func:`complex` can be used to produce numbers of a specific type.
272+
x + complex(u, v) = complex(x + u, v)
273+
x * complex(u, v) = complex(x * u, x * v)
274+
275+
A comparison between numbers of different types behaves as though the exact
276+
values of those numbers were being compared. [2]_
271277

272278
All numeric types (except complex) support the following operations (for priorities of
273279
the operations, see :ref:`operator-summary`):

Include/internal/pycore_floatobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ extern PyObject* _Py_string_to_number_with_underscores(
5454

5555
extern double _Py_parse_inf_or_nan(const char *p, char **endptr);
5656

57+
extern int _Py_convert_to_double(PyObject **v, double *dbl);
58+
5759

5860
#ifdef __cplusplus
5961
}

Lib/test/test_complex.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ def test_truediv(self):
127127
self.assertTrue(isnan(z.real))
128128
self.assertTrue(isnan(z.imag))
129129

130+
self.assertComplexesAreIdentical(complex(INF, NAN) / 2,
131+
complex(INF, NAN))
132+
130133
self.assertComplexesAreIdentical(complex(INF, 1)/(0.0+1j),
131134
complex(NAN, -INF))
132135

@@ -224,20 +227,32 @@ def check(n, deltas, is_equal, imag = 0.0):
224227
def test_add(self):
225228
self.assertEqual(1j + int(+1), complex(+1, 1))
226229
self.assertEqual(1j + int(-1), complex(-1, 1))
230+
self.assertComplexesAreIdentical(complex(-0.0, -0.0) + (-0.0),
231+
complex(-0.0, -0.0))
232+
self.assertComplexesAreIdentical((-0.0) + complex(-0.0, -0.0),
233+
complex(-0.0, -0.0))
227234
self.assertRaises(OverflowError, operator.add, 1j, 10**1000)
228235
self.assertRaises(TypeError, operator.add, 1j, None)
229236
self.assertRaises(TypeError, operator.add, None, 1j)
230237

231238
def test_sub(self):
232239
self.assertEqual(1j - int(+1), complex(-1, 1))
233240
self.assertEqual(1j - int(-1), complex(1, 1))
241+
self.assertComplexesAreIdentical(complex(-0.0, -0.0) - 0.0,
242+
complex(-0.0, -0.0))
243+
self.assertComplexesAreIdentical(-0.0 - complex(0.0, 0.0),
244+
complex(-0.0, -0.0))
234245
self.assertRaises(OverflowError, operator.sub, 1j, 10**1000)
235246
self.assertRaises(TypeError, operator.sub, 1j, None)
236247
self.assertRaises(TypeError, operator.sub, None, 1j)
237248

238249
def test_mul(self):
239250
self.assertEqual(1j * int(20), complex(0, 20))
240251
self.assertEqual(1j * int(-1), complex(0, -1))
252+
self.assertComplexesAreIdentical(complex(INF, NAN) * 2,
253+
complex(INF, NAN))
254+
self.assertComplexesAreIdentical(2 * complex(INF, NAN),
255+
complex(INF, NAN))
241256
self.assertRaises(OverflowError, operator.mul, 1j, 10**1000)
242257
self.assertRaises(TypeError, operator.mul, 1j, None)
243258
self.assertRaises(TypeError, operator.mul, None, 1j)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement mixed-mode arithmetic rules combining real and complex variables
2+
as specified by C standards since C99. Patch by Sergey B Kirpichev.

Objects/complexobject.c

Lines changed: 128 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "Python.h"
99
#include "pycore_call.h" // _PyObject_CallNoArgs()
1010
#include "pycore_complexobject.h" // _PyComplex_FormatAdvancedWriter()
11+
#include "pycore_floatobject.h" // _Py_convert_to_double()
1112
#include "pycore_long.h" // _PyLong_GetZero()
1213
#include "pycore_object.h" // _PyObject_Init()
1314
#include "pycore_pymath.h" // _Py_ADJUST_ERANGE2()
@@ -481,75 +482,163 @@ complex_hash(PyComplexObject *v)
481482
return (obj)
482483

483484
static int
484-
to_complex(PyObject **pobj, Py_complex *pc)
485+
to_float(PyObject **pobj, double *dbl)
485486
{
486487
PyObject *obj = *pobj;
487488

488-
pc->real = pc->imag = 0.0;
489-
if (PyLong_Check(obj)) {
490-
pc->real = PyLong_AsDouble(obj);
491-
if (pc->real == -1.0 && PyErr_Occurred()) {
492-
*pobj = NULL;
493-
return -1;
494-
}
495-
return 0;
496-
}
497489
if (PyFloat_Check(obj)) {
498-
pc->real = PyFloat_AsDouble(obj);
499-
return 0;
490+
*dbl = PyFloat_AS_DOUBLE(obj);
491+
}
492+
else if (_Py_convert_to_double(pobj, dbl) < 0) {
493+
return -1;
500494
}
501-
*pobj = Py_NewRef(Py_NotImplemented);
502-
return -1;
495+
return 0;
496+
}
497+
498+
static int
499+
to_complex(PyObject **pobj, Py_complex *pc)
500+
{
501+
pc->imag = 0.0;
502+
return to_float(pobj, &(pc->real));
503503
}
504504

505+
/* Complex arithmetic rules implement special mixed-mode case: combining
506+
pure-real (float's or int's) value and complex value performed directly, not
507+
by first coercing the real value to complex.
508+
509+
Lets consider the addition as an example, assuming that int's are implicitly
510+
converted to float's. We have following rules (up to variants with changed
511+
order of operands):
512+
513+
complex(x, y) + complex(u, v) = complex(x + u, y + v)
514+
float(x) + complex(u, v) = complex(x + u, v)
515+
516+
Similar rules are implemented for subtraction and multiplication. See C11's
517+
Annex G, sections G.5.1 and G.5.2. The true division is special:
518+
519+
complex(x, y) / float(u) = complex(x/u, y/u)
520+
float(x) / complex(u, v) = complex(x, 0) / complex(u, v)
521+
*/
505522

506523
static PyObject *
507524
complex_add(PyObject *v, PyObject *w)
508525
{
509-
Py_complex result;
510-
Py_complex a, b;
511-
TO_COMPLEX(v, a);
512-
TO_COMPLEX(w, b);
513-
result = _Py_c_sum(a, b);
514-
return PyComplex_FromCComplex(result);
526+
if (PyComplex_Check(w)) {
527+
PyObject *tmp = v;
528+
v = w;
529+
w = tmp;
530+
}
531+
532+
Py_complex a = ((PyComplexObject *)(v))->cval;
533+
double b;
534+
535+
if (PyComplex_Check(w)) {
536+
Py_complex b = ((PyComplexObject *)(w))->cval;
537+
a = _Py_c_sum(a, b);
538+
}
539+
else if (to_float(&w, &b) < 0) {
540+
return w;
541+
}
542+
else {
543+
a.real += b;
544+
}
545+
546+
return PyComplex_FromCComplex(a);
515547
}
516548

517549
static PyObject *
518550
complex_sub(PyObject *v, PyObject *w)
519551
{
520-
Py_complex result;
521-
Py_complex a, b;
522-
TO_COMPLEX(v, a);
523-
TO_COMPLEX(w, b);
524-
result = _Py_c_diff(a, b);
525-
return PyComplex_FromCComplex(result);
552+
Py_complex a;
553+
554+
if (PyComplex_Check(w)) {
555+
Py_complex b = ((PyComplexObject *)(w))->cval;
556+
557+
if (PyComplex_Check(v)) {
558+
a = ((PyComplexObject *)(v))->cval;
559+
errno = 0;
560+
a = _Py_c_diff(a, b);
561+
}
562+
else if (to_float(&v, &a.real) < 0) {
563+
return v;
564+
}
565+
else {
566+
a = (Py_complex) {a.real, -b.imag};
567+
a.real -= b.real;
568+
}
569+
}
570+
else {
571+
a = ((PyComplexObject *)(v))->cval;
572+
double b;
573+
574+
if (to_float(&w, &b) < 0) {
575+
return w;
576+
}
577+
a.real -= b;
578+
}
579+
580+
return PyComplex_FromCComplex(a);
526581
}
527582

528583
static PyObject *
529584
complex_mul(PyObject *v, PyObject *w)
530585
{
531-
Py_complex result;
532-
Py_complex a, b;
533-
TO_COMPLEX(v, a);
534-
TO_COMPLEX(w, b);
535-
result = _Py_c_prod(a, b);
536-
return PyComplex_FromCComplex(result);
586+
if (PyComplex_Check(w)) {
587+
PyObject *tmp = v;
588+
v = w;
589+
w = tmp;
590+
}
591+
592+
Py_complex a = ((PyComplexObject *)(v))->cval;
593+
double b;
594+
595+
if (PyComplex_Check(w)) {
596+
Py_complex b = ((PyComplexObject *)(w))->cval;
597+
a = _Py_c_prod(a, b);
598+
}
599+
else if (to_float(&w, &b) < 0) {
600+
return w;
601+
}
602+
else {
603+
a.real *= b;
604+
a.imag *= b;
605+
}
606+
607+
return PyComplex_FromCComplex(a);
537608
}
538609

539610
static PyObject *
540611
complex_div(PyObject *v, PyObject *w)
541612
{
542-
Py_complex quot;
543-
Py_complex a, b;
544-
TO_COMPLEX(v, a);
545-
TO_COMPLEX(w, b);
546-
errno = 0;
547-
quot = _Py_c_quot(a, b);
613+
Py_complex a;
614+
615+
if (PyComplex_Check(w)) {
616+
Py_complex b = ((PyComplexObject *)(w))->cval;
617+
TO_COMPLEX(v, a);
618+
errno = 0;
619+
a = _Py_c_quot(a, b);
620+
}
621+
else {
622+
double b;
623+
624+
if (to_float(&w, &b) < 0) {
625+
return w;
626+
}
627+
if (b) {
628+
a = ((PyComplexObject *)(v))->cval;
629+
a.real /= b;
630+
a.imag /= b;
631+
}
632+
else {
633+
errno = EDOM;
634+
}
635+
}
636+
548637
if (errno == EDOM) {
549638
PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
550639
return NULL;
551640
}
552-
return PyComplex_FromCComplex(quot);
641+
return PyComplex_FromCComplex(a);
553642
}
554643

555644
static PyObject *

Objects/floatobject.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,13 @@ PyFloat_AsDouble(PyObject *op)
309309
#define CONVERT_TO_DOUBLE(obj, dbl) \
310310
if (PyFloat_Check(obj)) \
311311
dbl = PyFloat_AS_DOUBLE(obj); \
312-
else if (convert_to_double(&(obj), &(dbl)) < 0) \
312+
else if (_Py_convert_to_double(&(obj), &(dbl)) < 0) \
313313
return obj;
314314

315315
/* Methods */
316316

317-
static int
318-
convert_to_double(PyObject **v, double *dbl)
317+
int
318+
_Py_convert_to_double(PyObject **v, double *dbl)
319319
{
320320
PyObject *obj = *v;
321321

Python/bltinmodule.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,7 +2742,6 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
27422742
double value = PyLong_AsDouble(item);
27432743
if (value != -1.0 || !PyErr_Occurred()) {
27442744
re_sum = cs_add(re_sum, value);
2745-
im_sum.hi += 0.0;
27462745
Py_DECREF(item);
27472746
continue;
27482747
}
@@ -2755,7 +2754,6 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
27552754
if (PyFloat_Check(item)) {
27562755
double value = PyFloat_AS_DOUBLE(item);
27572756
re_sum = cs_add(re_sum, value);
2758-
im_sum.hi += 0.0;
27592757
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
27602758
continue;
27612759
}

0 commit comments

Comments
 (0)