Commit 808354bd authored by Mark Dickinson's avatar Mark Dickinson

Merged revisions 65258,65292,65299,65308-65309,65315,65326 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r65258 | mark.dickinson | 2008-07-27 08:15:29 +0100 (Sun, 27 Jul 2008) | 4 lines

  Remove math.sum tests related to overflow, special values, and behaviour
  near the extremes of the floating-point range.  (The behaviour of math.sum
  should be regarded as undefined in these cases.)
........
  r65292 | mark.dickinson | 2008-07-29 19:45:38 +0100 (Tue, 29 Jul 2008) | 4 lines

  More modifications to tests for math.sum:  replace the Python
  version of msum by a version using a different algorithm, and
  use the new float.fromhex method to specify test results exactly.
........
  r65299 | mark.dickinson | 2008-07-30 13:01:41 +0100 (Wed, 30 Jul 2008) | 5 lines

  Fix special-value handling for math.sum.
  Also minor cleanups to the code: fix tabbing, remove
  trailing whitespace, and reformat to fit into 80
  columns.
........
  r65308 | mark.dickinson | 2008-07-30 17:20:10 +0100 (Wed, 30 Jul 2008) | 2 lines

  Rename math.sum to math.fsum
........
  r65309 | mark.dickinson | 2008-07-30 17:25:16 +0100 (Wed, 30 Jul 2008) | 3 lines

  Replace math.sum with math.fsum in a couple of comments
  that were missed by r65308
........
  r65315 | mark.dickinson | 2008-07-30 21:23:15 +0100 (Wed, 30 Jul 2008) | 2 lines

  Add note about problems with math.fsum on x86 hardware.
........
  r65326 | mark.dickinson | 2008-07-31 15:48:32 +0100 (Thu, 31 Jul 2008) | 2 lines

  Rename testSum to testFsum and move it to proper place in test_math.py
........
parent 4833da85
...@@ -76,6 +76,42 @@ Number-theoretic and representation functions: ...@@ -76,6 +76,42 @@ Number-theoretic and representation functions:
apart" the internal representation of a float in a portable way. apart" the internal representation of a float in a portable way.
.. function:: fsum(iterable)
Return an accurate floating point sum of values in the iterable. Avoids
loss of precision by tracking multiple intermediate partial sums. The
algorithm's accuracy depends on IEEE-754 arithmetic guarantees and the
typical case where the rounding mode is half-even.
.. note::
On platforms where arithmetic results are not correctly rounded,
:func:`fsum` may occasionally produce incorrect results; these
results should be no less accurate than those from the builtin
:func:`sum` function, but nevertheless may have arbitrarily
large relative error.
In particular, this affects some older Intel hardware (for
example Pentium and earlier x86 processors) that makes use of
'extended precision' floating-point registers with 64 bits of
precision instead of the 53 bits of precision provided by a C
double. Arithmetic operations using these registers may be
doubly rounded (rounded first to 64 bits, and then rerounded to
53 bits), leading to incorrectly rounded results. To test
whether your machine is one of those affected, try the following
at a Python prompt::
>>> 1e16 + 2.9999
10000000000000002.0
Machines subject to the double-rounding problem described above
are likely to print ``10000000000000004.0`` instead of
``10000000000000002.0``.
.. versionadded:: 2.6
.. function:: isinf(x) .. function:: isinf(x)
Checks if the float *x* is positive or negative infinite. Checks if the float *x* is positive or negative infinite.
...@@ -100,12 +136,6 @@ Number-theoretic and representation functions: ...@@ -100,12 +136,6 @@ Number-theoretic and representation functions:
Return the fractional and integer parts of *x*. Both results carry the sign of Return the fractional and integer parts of *x*. Both results carry the sign of
*x*, and both are floats. *x*, and both are floats.
.. function:: sum(iterable)
Return an accurate floating point sum of values in the iterable. Avoids
loss of precision by tracking multiple intermediate partial sums. The
algorithm's accuracy depends on IEEE-754 arithmetic guarantees and the
typical case where the rounding mode is half-even.
.. function:: trunc(x) .. function:: trunc(x)
......
...@@ -1537,7 +1537,7 @@ Here are all of the changes that Python 2.6 makes to the core Python language. ...@@ -1537,7 +1537,7 @@ Here are all of the changes that Python 2.6 makes to the core Python language.
* :func:`~math.factorial` computes the factorial of a number. * :func:`~math.factorial` computes the factorial of a number.
(Contributed by Raymond Hettinger; :issue:`2138`.) (Contributed by Raymond Hettinger; :issue:`2138`.)
* :func:`~math.sum` adds up the stream of numbers from an iterable, * :func:`~math.fsum` adds up the stream of numbers from an iterable,
and is careful to avoid loss of precision by calculating partial sums. and is careful to avoid loss of precision by calculating partial sums.
(Contributed by Jean Brouwers, Raymond Hettinger, and Mark Dickinson; (Contributed by Jean Brouwers, Raymond Hettinger, and Mark Dickinson;
:issue:`2819`.) :issue:`2819`.)
......
This diff is collapsed.
...@@ -5,7 +5,7 @@ import random ...@@ -5,7 +5,7 @@ import random
import time import time
import pickle import pickle
import warnings import warnings
from math import log, exp, sqrt, pi, sum as msum from math import log, exp, sqrt, pi, fsum as msum
from test import support from test import support
class TestBasicOps(unittest.TestCase): class TestBasicOps(unittest.TestCase):
......
...@@ -396,7 +396,7 @@ FUNC1(tanh, tanh, 0, ...@@ -396,7 +396,7 @@ FUNC1(tanh, tanh, 0,
Note 4: A similar implementation is in Modules/cmathmodule.c. Note 4: A similar implementation is in Modules/cmathmodule.c.
Be sure to update both when making changes. Be sure to update both when making changes.
Note 5: The signature of math.sum() differs from __builtin__.sum() Note 5: The signature of math.fsum() differs from __builtin__.sum()
because the start argument doesn't make sense in the context of because the start argument doesn't make sense in the context of
accurate summation. Since the partials table is collapsed before accurate summation. Since the partials table is collapsed before
returning a result, sum(seq2, start=sum(seq1)) may not equal the returning a result, sum(seq2, start=sum(seq1)) may not equal the
...@@ -407,7 +407,7 @@ FUNC1(tanh, tanh, 0, ...@@ -407,7 +407,7 @@ FUNC1(tanh, tanh, 0,
/* Extend the partials array p[] by doubling its size. */ /* Extend the partials array p[] by doubling its size. */
static int /* non-zero on error */ static int /* non-zero on error */
_sum_realloc(double **p_ptr, Py_ssize_t n, _fsum_realloc(double **p_ptr, Py_ssize_t n,
double *ps, Py_ssize_t *m_ptr) double *ps, Py_ssize_t *m_ptr)
{ {
void *v = NULL; void *v = NULL;
...@@ -425,7 +425,7 @@ _sum_realloc(double **p_ptr, Py_ssize_t n, ...@@ -425,7 +425,7 @@ _sum_realloc(double **p_ptr, Py_ssize_t n,
v = PyMem_Realloc(p, sizeof(double) * m); v = PyMem_Realloc(p, sizeof(double) * m);
} }
if (v == NULL) { /* size overflow or no memory */ if (v == NULL) { /* size overflow or no memory */
PyErr_SetString(PyExc_MemoryError, "math sum partials"); PyErr_SetString(PyExc_MemoryError, "math.fsum partials");
return 1; return 1;
} }
*p_ptr = (double*) v; *p_ptr = (double*) v;
...@@ -464,18 +464,19 @@ _sum_realloc(double **p_ptr, Py_ssize_t n, ...@@ -464,18 +464,19 @@ _sum_realloc(double **p_ptr, Py_ssize_t n,
*/ */
static PyObject* static PyObject*
math_sum(PyObject *self, PyObject *seq) math_fsum(PyObject *self, PyObject *seq)
{ {
PyObject *item, *iter, *sum = NULL; PyObject *item, *iter, *sum = NULL;
Py_ssize_t i, j, n = 0, m = NUM_PARTIALS; Py_ssize_t i, j, n = 0, m = NUM_PARTIALS;
double x, y, t, ps[NUM_PARTIALS], *p = ps; double x, y, t, ps[NUM_PARTIALS], *p = ps;
double xsave, special_sum = 0.0, inf_sum = 0.0;
volatile double hi, yr, lo; volatile double hi, yr, lo;
iter = PyObject_GetIter(seq); iter = PyObject_GetIter(seq);
if (iter == NULL) if (iter == NULL)
return NULL; return NULL;
PyFPE_START_PROTECT("sum", Py_DECREF(iter); return NULL) PyFPE_START_PROTECT("fsum", Py_DECREF(iter); return NULL)
for(;;) { /* for x in iterable */ for(;;) { /* for x in iterable */
assert(0 <= n && n <= m); assert(0 <= n && n <= m);
...@@ -485,18 +486,19 @@ math_sum(PyObject *self, PyObject *seq) ...@@ -485,18 +486,19 @@ math_sum(PyObject *self, PyObject *seq)
item = PyIter_Next(iter); item = PyIter_Next(iter);
if (item == NULL) { if (item == NULL) {
if (PyErr_Occurred()) if (PyErr_Occurred())
goto _sum_error; goto _fsum_error;
break; break;
} }
x = PyFloat_AsDouble(item); x = PyFloat_AsDouble(item);
Py_DECREF(item); Py_DECREF(item);
if (PyErr_Occurred()) if (PyErr_Occurred())
goto _sum_error; goto _fsum_error;
xsave = x;
for (i = j = 0; j < n; j++) { /* for y in partials */ for (i = j = 0; j < n; j++) { /* for y in partials */
y = p[j]; y = p[j];
if (fabs(x) < fabs(y)) { if (fabs(x) < fabs(y)) {
t = x; x = y; y = t; t = x; x = y; y = t;
} }
hi = x + y; hi = x + y;
yr = hi - x; yr = hi - x;
...@@ -505,59 +507,73 @@ math_sum(PyObject *self, PyObject *seq) ...@@ -505,59 +507,73 @@ math_sum(PyObject *self, PyObject *seq)
p[i++] = lo; p[i++] = lo;
x = hi; x = hi;
} }
n = i; /* ps[i:] = [x] */ n = i; /* ps[i:] = [x] */
if (x != 0.0) { if (x != 0.0) {
/* If non-finite, reset partials, effectively if (! Py_IS_FINITE(x)) {
adding subsequent items without roundoff /* a nonfinite x could arise either as
and yielding correct non-finite results, a result of intermediate overflow, or
provided IEEE 754 rules are observed */ as a result of a nan or inf in the
if (! Py_IS_FINITE(x)) summands */
if (Py_IS_FINITE(xsave)) {
PyErr_SetString(PyExc_OverflowError,
"intermediate overflow in fsum");
goto _fsum_error;
}
if (Py_IS_INFINITY(xsave))
inf_sum += xsave;
special_sum += xsave;
/* reset partials */
n = 0; n = 0;
else if (n >= m && _sum_realloc(&p, n, ps, &m)) }
goto _sum_error; else if (n >= m && _fsum_realloc(&p, n, ps, &m))
p[n++] = x; goto _fsum_error;
else
p[n++] = x;
} }
} }
if (special_sum != 0.0) {
if (Py_IS_NAN(inf_sum))
PyErr_SetString(PyExc_ValueError,
"-inf + inf in fsum");
else
sum = PyFloat_FromDouble(special_sum);
goto _fsum_error;
}
hi = 0.0; hi = 0.0;
if (n > 0) { if (n > 0) {
hi = p[--n]; hi = p[--n];
if (Py_IS_FINITE(hi)) { /* sum_exact(ps, hi) from the top, stop when the sum becomes
/* sum_exact(ps, hi) from the top, stop when the sum becomes inexact. */ inexact. */
while (n > 0) { while (n > 0) {
x = hi; x = hi;
y = p[--n]; y = p[--n];
assert(fabs(y) < fabs(x)); assert(fabs(y) < fabs(x));
hi = x + y; hi = x + y;
yr = hi - x; yr = hi - x;
lo = y - yr; lo = y - yr;
if (lo != 0.0) if (lo != 0.0)
break; break;
}
/* Make half-even rounding work across multiple partials. Needed
so that sum([1e-16, 1, 1e16]) will round-up the last digit to
two instead of down to zero (the 1e-16 makes the 1 slightly
closer to two). With a potential 1 ULP rounding error fixed-up,
math.sum() can guarantee commutativity. */
if (n > 0 && ((lo < 0.0 && p[n-1] < 0.0) ||
(lo > 0.0 && p[n-1] > 0.0))) {
y = lo * 2.0;
x = hi + y;
yr = x - hi;
if (y == yr)
hi = x;
}
} }
else { /* raise exception corresponding to a special value */ /* Make half-even rounding work across multiple partials.
errno = Py_IS_NAN(hi) ? EDOM : ERANGE; Needed so that sum([1e-16, 1, 1e16]) will round-up the last
if (is_error(hi)) digit to two instead of down to zero (the 1e-16 makes the 1
goto _sum_error; slightly closer to two). With a potential 1 ULP rounding
error fixed-up, math.fsum() can guarantee commutativity. */
if (n > 0 && ((lo < 0.0 && p[n-1] < 0.0) ||
(lo > 0.0 && p[n-1] > 0.0))) {
y = lo * 2.0;
x = hi + y;
yr = x - hi;
if (y == yr)
hi = x;
} }
} }
sum = PyFloat_FromDouble(hi); sum = PyFloat_FromDouble(hi);
_sum_error: _fsum_error:
PyFPE_END_PROTECT(hi) PyFPE_END_PROTECT(hi)
Py_DECREF(iter); Py_DECREF(iter);
if (p != ps) if (p != ps)
...@@ -567,7 +583,7 @@ _sum_error: ...@@ -567,7 +583,7 @@ _sum_error:
#undef NUM_PARTIALS #undef NUM_PARTIALS
PyDoc_STRVAR(math_sum_doc, PyDoc_STRVAR(math_fsum_doc,
"sum(iterable)\n\n\ "sum(iterable)\n\n\
Return an accurate floating point sum of values in the iterable.\n\ Return an accurate floating point sum of values in the iterable.\n\
Assumes IEEE-754 floating point arithmetic."); Assumes IEEE-754 floating point arithmetic.");
...@@ -1078,6 +1094,7 @@ static PyMethodDef math_methods[] = { ...@@ -1078,6 +1094,7 @@ static PyMethodDef math_methods[] = {
{"floor", math_floor, METH_O, math_floor_doc}, {"floor", math_floor, METH_O, math_floor_doc},
{"fmod", math_fmod, METH_VARARGS, math_fmod_doc}, {"fmod", math_fmod, METH_VARARGS, math_fmod_doc},
{"frexp", math_frexp, METH_O, math_frexp_doc}, {"frexp", math_frexp, METH_O, math_frexp_doc},
{"fsum", math_fsum, METH_O, math_fsum_doc},
{"hypot", math_hypot, METH_VARARGS, math_hypot_doc}, {"hypot", math_hypot, METH_VARARGS, math_hypot_doc},
{"isinf", math_isinf, METH_O, math_isinf_doc}, {"isinf", math_isinf, METH_O, math_isinf_doc},
{"isnan", math_isnan, METH_O, math_isnan_doc}, {"isnan", math_isnan, METH_O, math_isnan_doc},
...@@ -1091,10 +1108,9 @@ static PyMethodDef math_methods[] = { ...@@ -1091,10 +1108,9 @@ static PyMethodDef math_methods[] = {
{"sin", math_sin, METH_O, math_sin_doc}, {"sin", math_sin, METH_O, math_sin_doc},
{"sinh", math_sinh, METH_O, math_sinh_doc}, {"sinh", math_sinh, METH_O, math_sinh_doc},
{"sqrt", math_sqrt, METH_O, math_sqrt_doc}, {"sqrt", math_sqrt, METH_O, math_sqrt_doc},
{"sum", math_sum, METH_O, math_sum_doc},
{"tan", math_tan, METH_O, math_tan_doc}, {"tan", math_tan, METH_O, math_tan_doc},
{"tanh", math_tanh, METH_O, math_tanh_doc}, {"tanh", math_tanh, METH_O, math_tanh_doc},
{"trunc", math_trunc, METH_O, math_trunc_doc}, {"trunc", math_trunc, METH_O, math_trunc_doc},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment