Skip to content

Commit e69b14a

Browse files
committed
pythongh-121149: improve accuracy of builtin sum() for complex inputs
1 parent 2cb84b1 commit e69b14a

File tree

4 files changed

+108
-25
lines changed

4 files changed

+108
-25
lines changed

Doc/library/functions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,9 @@ are always available. They are listed here in alphabetical order.
19341934
.. versionchanged:: 3.12 Summation of floats switched to an algorithm
19351935
that gives higher accuracy and better commutativity on most builds.
19361936

1937+
.. versionchanged:: 3.14 Added specialization for summation of complexes,
1938+
using same algorithm as for summation of floats.
1939+
19371940

19381941
.. class:: super()
19391942
super(type, object_or_type=None)

Lib/test/test_builtin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,10 @@ def __getitem__(self, index):
17751775
def test_sum_accuracy(self):
17761776
self.assertEqual(sum([0.1] * 10), 1.0)
17771777
self.assertEqual(sum([1.0, 10E100, 1.0, -10E100]), 2.0)
1778+
self.assertEqual(sum([1.0, 10E100, 1.0, -10E100, 2j]), 2+2j)
1779+
self.assertEqual(sum([2+1j, 10E100j, 1j, -10E100j]), 2+2j)
1780+
self.assertEqual(sum([1j, 1, 10E100j, 1j, 1.0, -10E100j]), 2+2j)
1781+
self.assertEqual(sum([0.1j]*10 + [fractions.Fraction(1, 10)]), 0.1+1j)
17781782

17791783
def test_type(self):
17801784
self.assertEqual(type(''), type('123'))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added specialization for summation of complexes, this also improves accuracy
2+
of builtin :func:`sum` for such inputs. Patch by Sergey B Kirpichev.

Python/bltinmodule.c

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,41 @@ Without arguments, equivalent to locals().\n\
25162516
With an argument, equivalent to object.__dict__.");
25172517

25182518

2519+
/* Improved Kahan–Babuška algorithm by Arnold Neumaier
2520+
Neumaier, A. (1974), Rundungsfehleranalyse einiger Verfahren
2521+
zur Summation endlicher Summen. Z. angew. Math. Mech.,
2522+
54: 39-51. https://doi.org/10.1002/zamm.19740540106
2523+
https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements
2524+
*/
2525+
2526+
typedef struct {
2527+
double sum; /* accumulator */
2528+
double c; /* a running compensation for lost low-order bits */
2529+
} _csum;
2530+
2531+
static inline void
2532+
_csum_neumaier_step(_csum *v, double x)
2533+
{
2534+
double t = v->sum + x;
2535+
if (fabs(v->sum) >= fabs(x)) {
2536+
v->c += (v->sum - t) + x;
2537+
} else {
2538+
v->c += (x - t) + v->sum;
2539+
}
2540+
v->sum = t;
2541+
}
2542+
2543+
static inline void
2544+
_csum_neumaier_finalize(_csum *v)
2545+
{
2546+
/* Avoid losing the sign on a negative result,
2547+
and don't let adding the compensation convert
2548+
an infinite or overflowed sum to a NaN. */
2549+
if (v->c && isfinite(v->c)) {
2550+
v->sum += v->c;
2551+
}
2552+
}
2553+
25192554
/*[clinic input]
25202555
sum as builtin_sum
25212556
@@ -2628,37 +2663,69 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
26282663
}
26292664

26302665
if (PyFloat_CheckExact(result)) {
2631-
double f_result = PyFloat_AS_DOUBLE(result);
2632-
double c = 0.0;
2666+
_csum f_result = {PyFloat_AS_DOUBLE(result), 0.0};
26332667
Py_SETREF(result, NULL);
26342668
while(result == NULL) {
26352669
item = PyIter_Next(iter);
26362670
if (item == NULL) {
26372671
Py_DECREF(iter);
26382672
if (PyErr_Occurred())
26392673
return NULL;
2640-
/* Avoid losing the sign on a negative result,
2641-
and don't let adding the compensation convert
2642-
an infinite or overflowed sum to a NaN. */
2643-
if (c && isfinite(c)) {
2644-
f_result += c;
2645-
}
2646-
return PyFloat_FromDouble(f_result);
2674+
_csum_neumaier_finalize(&f_result);
2675+
return PyFloat_FromDouble(f_result.sum);
26472676
}
26482677
if (PyFloat_CheckExact(item)) {
2649-
// Improved Kahan–Babuška algorithm by Arnold Neumaier
2650-
// Neumaier, A. (1974), Rundungsfehleranalyse einiger Verfahren
2651-
// zur Summation endlicher Summen. Z. angew. Math. Mech.,
2652-
// 54: 39-51. https://doi.org/10.1002/zamm.19740540106
2653-
// https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements
2654-
double x = PyFloat_AS_DOUBLE(item);
2655-
double t = f_result + x;
2656-
if (fabs(f_result) >= fabs(x)) {
2657-
c += (f_result - t) + x;
2658-
} else {
2659-
c += (x - t) + f_result;
2678+
_csum_neumaier_step(&f_result, PyFloat_AS_DOUBLE(item));
2679+
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
2680+
continue;
2681+
}
2682+
if (PyLong_Check(item)) {
2683+
long value;
2684+
int overflow;
2685+
value = PyLong_AsLongAndOverflow(item, &overflow);
2686+
if (!overflow) {
2687+
f_result.sum += (double)value;
2688+
Py_DECREF(item);
2689+
continue;
26602690
}
2661-
f_result = t;
2691+
}
2692+
_csum_neumaier_finalize(&f_result);
2693+
result = PyFloat_FromDouble(f_result.sum);
2694+
if (result == NULL) {
2695+
Py_DECREF(item);
2696+
Py_DECREF(iter);
2697+
return NULL;
2698+
}
2699+
temp = PyNumber_Add(result, item);
2700+
Py_DECREF(result);
2701+
Py_DECREF(item);
2702+
result = temp;
2703+
if (result == NULL) {
2704+
Py_DECREF(iter);
2705+
return NULL;
2706+
}
2707+
}
2708+
}
2709+
2710+
if (PyComplex_CheckExact(result)) {
2711+
Py_complex z = PyComplex_AsCComplex(result);
2712+
_csum cr_result = {z.real, 0.0};
2713+
_csum ci_result = {z.imag, 0.0};
2714+
Py_SETREF(result, NULL);
2715+
while(result == NULL) {
2716+
item = PyIter_Next(iter);
2717+
if (item == NULL) {
2718+
Py_DECREF(iter);
2719+
if (PyErr_Occurred())
2720+
return NULL;
2721+
_csum_neumaier_finalize(&cr_result);
2722+
_csum_neumaier_finalize(&ci_result);
2723+
return PyComplex_FromDoubles(cr_result.sum, ci_result.sum);
2724+
}
2725+
if (PyComplex_CheckExact(item)) {
2726+
z = PyComplex_AsCComplex(item);
2727+
_csum_neumaier_step(&cr_result, z.real);
2728+
_csum_neumaier_step(&ci_result, z.imag);
26622729
_Py_DECREF_SPECIALIZED(item, _PyFloat_ExactDealloc);
26632730
continue;
26642731
}
@@ -2667,15 +2734,22 @@ builtin_sum_impl(PyObject *module, PyObject *iterable, PyObject *start)
26672734
int overflow;
26682735
value = PyLong_AsLongAndOverflow(item, &overflow);
26692736
if (!overflow) {
2670-
f_result += (double)value;
2737+
cr_result.sum += (double)value;
2738+
ci_result.sum += 0.0;
26712739
Py_DECREF(item);
26722740
continue;
26732741
}
26742742
}
2675-
if (c && isfinite(c)) {
2676-
f_result += c;
2743+
if (PyFloat_Check(item)) {
2744+
double value = PyFloat_AS_DOUBLE(item);
2745+
cr_result.sum += value;
2746+
ci_result.sum += 0.0;
2747+
Py_DECREF(item);
2748+
continue;
26772749
}
2678-
result = PyFloat_FromDouble(f_result);
2750+
_csum_neumaier_finalize(&cr_result);
2751+
_csum_neumaier_finalize(&ci_result);
2752+
result = PyComplex_FromDoubles(cr_result.sum, ci_result.sum);
26792753
if (result == NULL) {
26802754
Py_DECREF(item);
26812755
Py_DECREF(iter);

0 commit comments

Comments
 (0)