Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aa7633a

Browse files
committed
Merged revisions 65258,65292,65299,65308-65309,65315,65326 via svnmerge from
svn+ssh://[email protected]/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 ........
1 parent c57df32 commit aa7633a

5 files changed

Lines changed: 201 additions & 211 deletions

File tree

Doc/library/math.rst

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,42 @@ Number-theoretic and representation functions:
7676
apart" the internal representation of a float in a portable way.
7777

7878

79+
.. function:: fsum(iterable)
80+
81+
Return an accurate floating point sum of values in the iterable. Avoids
82+
loss of precision by tracking multiple intermediate partial sums. The
83+
algorithm's accuracy depends on IEEE-754 arithmetic guarantees and the
84+
typical case where the rounding mode is half-even.
85+
86+
.. note::
87+
88+
On platforms where arithmetic results are not correctly rounded,
89+
:func:`fsum` may occasionally produce incorrect results; these
90+
results should be no less accurate than those from the builtin
91+
:func:`sum` function, but nevertheless may have arbitrarily
92+
large relative error.
93+
94+
In particular, this affects some older Intel hardware (for
95+
example Pentium and earlier x86 processors) that makes use of
96+
'extended precision' floating-point registers with 64 bits of
97+
precision instead of the 53 bits of precision provided by a C
98+
double. Arithmetic operations using these registers may be
99+
doubly rounded (rounded first to 64 bits, and then rerounded to
100+
53 bits), leading to incorrectly rounded results. To test
101+
whether your machine is one of those affected, try the following
102+
at a Python prompt::
103+
104+
>>> 1e16 + 2.9999
105+
10000000000000002.0
106+
107+
Machines subject to the double-rounding problem described above
108+
are likely to print ``10000000000000004.0`` instead of
109+
``10000000000000002.0``.
110+
111+
112+
.. versionadded:: 2.6
113+
114+
79115
.. function:: isinf(x)
80116

81117
Checks if the float *x* is positive or negative infinite.
@@ -100,12 +136,6 @@ Number-theoretic and representation functions:
100136
Return the fractional and integer parts of *x*. Both results carry the sign of
101137
*x*, and both are floats.
102138

103-
.. function:: sum(iterable)
104-
105-
Return an accurate floating point sum of values in the iterable. Avoids
106-
loss of precision by tracking multiple intermediate partial sums. The
107-
algorithm's accuracy depends on IEEE-754 arithmetic guarantees and the
108-
typical case where the rounding mode is half-even.
109139

110140
.. function:: trunc(x)
111141

Doc/whatsnew/2.6.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ Here are all of the changes that Python 2.6 makes to the core Python language.
15371537
* :func:`~math.factorial` computes the factorial of a number.
15381538
(Contributed by Raymond Hettinger; :issue:`2138`.)
15391539

1540-
* :func:`~math.sum` adds up the stream of numbers from an iterable,
1540+
* :func:`~math.fsum` adds up the stream of numbers from an iterable,
15411541
and is careful to avoid loss of precision by calculating partial sums.
15421542
(Contributed by Jean Brouwers, Raymond Hettinger, and Mark Dickinson;
15431543
:issue:`2819`.)

Lib/test/test_math.py

Lines changed: 96 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,102 @@ def testfrexp(name, result, expected):
359359
self.assertEquals(math.frexp(NINF)[0], NINF)
360360
self.assert_(math.isnan(math.frexp(NAN)[0]))
361361

362+
def testFsum(self):
363+
# math.fsum relies on exact rounding for correct operation.
364+
# There's a known problem with IA32 floating-point that causes
365+
# inexact rounding in some situations, and will cause the
366+
# math.fsum tests below to fail; see issue #2937. On non IEEE
367+
# 754 platforms, and on IEEE 754 platforms that exhibit the
368+
# problem described in issue #2937, we simply skip the whole
369+
# test.
370+
371+
if not float.__getformat__("double").startswith("IEEE"):
372+
return
373+
374+
# on IEEE 754 compliant machines, both of the expressions
375+
# below should round to 10000000000000002.0.
376+
if 1e16+2.0 != 1e16+2.9999:
377+
return
378+
379+
# Python version of math.fsum, for comparison. Uses a
380+
# different algorithm based on frexp, ldexp and integer
381+
# arithmetic.
382+
from sys import float_info
383+
mant_dig = float_info.mant_dig
384+
etiny = float_info.min_exp - mant_dig
385+
386+
def msum(iterable):
387+
"""Full precision summation. Compute sum(iterable) without any
388+
intermediate accumulation of error. Based on the 'lsum' function
389+
at http://code.activestate.com/recipes/393090/
390+
391+
"""
392+
tmant, texp = 0, 0
393+
for x in iterable:
394+
mant, exp = math.frexp(x)
395+
mant, exp = int(math.ldexp(mant, mant_dig)), exp - mant_dig
396+
if texp > exp:
397+
tmant <<= texp-exp
398+
texp = exp
399+
else:
400+
mant <<= exp-texp
401+
tmant += mant
402+
# Round tmant * 2**texp to a float. The original recipe
403+
# used float(str(tmant)) * 2.0**texp for this, but that's
404+
# a little unsafe because str -> float conversion can't be
405+
# relied upon to do correct rounding on all platforms.
406+
tail = max(len(bin(abs(tmant)))-2 - mant_dig, etiny - texp)
407+
if tail > 0:
408+
h = 1 << (tail-1)
409+
tmant = tmant // (2*h) + bool(tmant & h and tmant & 3*h-1)
410+
texp += tail
411+
return math.ldexp(tmant, texp)
412+
413+
test_values = [
414+
([], 0.0),
415+
([0.0], 0.0),
416+
([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
417+
([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
418+
([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
419+
([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
420+
([2.0**53-4.0, 0.5, 2.0**-54], 2.0**53-3.0),
421+
([1./n for n in range(1, 1001)],
422+
float.fromhex('0x1.df11f45f4e61ap+2')),
423+
([(-1.)**n/n for n in range(1, 1001)],
424+
float.fromhex('-0x1.62a2af1bd3624p-1')),
425+
([1.7**(i+1)-1.7**i for i in range(1000)] + [-1.7**1000], -1.0),
426+
([1e16, 1., 1e-16], 10000000000000002.0),
427+
([1e16-2., 1.-2.**-53, -(1e16-2.), -(1.-2.**-53)], 0.0),
428+
# exercise code for resizing partials array
429+
([2.**n - 2.**(n+50) + 2.**(n+52) for n in range(-1074, 972, 2)] +
430+
[-2.**1022],
431+
float.fromhex('0x1.5555555555555p+970')),
432+
]
433+
434+
for i, (vals, expected) in enumerate(test_values):
435+
try:
436+
actual = math.fsum(vals)
437+
except OverflowError:
438+
self.fail("test %d failed: got OverflowError, expected %r "
439+
"for math.fsum(%.100r)" % (i, expected, vals))
440+
except ValueError:
441+
self.fail("test %d failed: got ValueError, expected %r "
442+
"for math.fsum(%.100r)" % (i, expected, vals))
443+
self.assertEqual(actual, expected)
444+
445+
from random import random, gauss, shuffle
446+
for j in range(1000):
447+
vals = [7, 1e100, -7, -1e100, -9e-20, 8e-20] * 10
448+
s = 0
449+
for i in range(200):
450+
v = gauss(0, random()) ** 7 - s
451+
s += v
452+
vals.append(v)
453+
shuffle(vals)
454+
455+
s = msum(vals)
456+
self.assertEqual(msum(vals), math.fsum(vals))
457+
362458
def testHypot(self):
363459
self.assertRaises(TypeError, math.hypot)
364460
self.ftest('hypot(0,0)', math.hypot(0,0), 0)
@@ -641,158 +737,6 @@ def testSqrt(self):
641737
self.assertRaises(ValueError, math.sqrt, NINF)
642738
self.assert_(math.isnan(math.sqrt(NAN)))
643739

644-
def testSum(self):
645-
# math.sum relies on exact rounding for correct operation.
646-
# There's a known problem with IA32 floating-point that causes
647-
# inexact rounding in some situations, and will cause the
648-
# math.sum tests below to fail; see issue #2937. On non IEEE
649-
# 754 platforms, and on IEEE 754 platforms that exhibit the
650-
# problem described in issue #2937, we simply skip the whole
651-
# test.
652-
653-
if not float.__getformat__("double").startswith("IEEE"):
654-
return
655-
656-
# on IEEE 754 compliant machines, both of the expressions
657-
# below should round to 10000000000000002.0.
658-
if 1e16+2.999 != 1e16+2.9999:
659-
return
660-
661-
# Python version of math.sum algorithm, for comparison
662-
def msum(iterable):
663-
"""Full precision sum of values in iterable. Returns the value of
664-
the sum, rounded to the nearest representable floating-point number
665-
using the round-half-to-even rule.
666-
667-
"""
668-
# Stage 1: accumulate partials
669-
partials = []
670-
for x in iterable:
671-
i = 0
672-
for y in partials:
673-
if abs(x) < abs(y):
674-
x, y = y, x
675-
hi = x + y
676-
lo = y - (hi - x)
677-
if lo:
678-
partials[i] = lo
679-
i += 1
680-
x = hi
681-
partials[i:] = [x] if x else []
682-
683-
# Stage 2: sum partials
684-
if not partials:
685-
return 0.0
686-
687-
# sum from the top, stopping as soon as the sum is inexact.
688-
total = partials.pop()
689-
while partials:
690-
x = partials.pop()
691-
old_total, total = total, total + x
692-
error = x - (total - old_total)
693-
if error != 0.0:
694-
# adjust for correct rounding if necessary
695-
if partials and (partials[-1] > 0.0) == (error > 0.0) and \
696-
total + 2*error - total == 2*error:
697-
total += 2*error
698-
break
699-
return total
700-
701-
from sys import float_info
702-
maxfloat = float_info.max
703-
twopow = 2.**(float_info.max_exp - 1)
704-
705-
test_values = [
706-
([], 0.0),
707-
([0.0], 0.0),
708-
([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50], 1e-100),
709-
([1e308, 1e308, -1e308], OverflowError),
710-
([-1e308, 1e308, 1e308], 1e308),
711-
([1e308, -1e308, 1e308], 1e308),
712-
([2.0**1023, 2.0**1023, -2.0**1000], OverflowError),
713-
([twopow, twopow, twopow, twopow, -twopow, -twopow, -twopow],
714-
OverflowError),
715-
([2.0**53, -0.5, -2.0**-54], 2.0**53-1.0),
716-
([2.0**53, 1.0, 2.0**-100], 2.0**53+2.0),
717-
([2.0**53+10.0, 1.0, 2.0**-100], 2.0**53+12.0),
718-
719-
([2.0**53-4.0, 0.5, 2.0**-54], 2.0**53-3.0),
720-
([2.0**1023-2.0**970, -1.0, 2.0**1023], OverflowError),
721-
([maxfloat, maxfloat*2.**-54], maxfloat),
722-
([maxfloat, maxfloat*2.**-53], OverflowError),
723-
([1./n for n in range(1, 1001)], 7.4854708605503451),
724-
([(-1.)**n/n for n in range(1, 1001)], -0.69264743055982025),
725-
([1.7**(i+1)-1.7**i for i in range(1000)] + [-1.7**1000], -1.0),
726-
([INF, -INF, NAN], ValueError),
727-
([NAN, INF, -INF], ValueError),
728-
([INF, NAN, INF], ValueError),
729-
730-
([INF, INF], OverflowError),
731-
([INF, -INF], ValueError),
732-
([-INF, 1e308, 1e308, -INF], OverflowError),
733-
([2.0**1023-2.0**970, 0.0, 2.0**1023], OverflowError),
734-
([2.0**1023-2.0**970, 1.0, 2.0**1023], OverflowError),
735-
([2.0**1023, 2.0**1023], OverflowError),
736-
([2.0**1023, 2.0**1023, -1.0], OverflowError),
737-
([twopow, twopow, twopow, twopow, -twopow, -twopow],
738-
OverflowError),
739-
([twopow, twopow, twopow, twopow, -twopow, twopow], OverflowError),
740-
([-twopow, -twopow, -twopow, -twopow], OverflowError),
741-
742-
([2.**1023, 2.**1023, -2.**971], OverflowError),
743-
([2.**1023, 2.**1023, -2.**970], OverflowError),
744-
([-2.**970, 2.**1023, 2.**1023, -2.**-1074], OverflowError),
745-
([ 2.**1023, 2.**1023, -2.**970, 2.**-1074], OverflowError),
746-
([-2.**1023, 2.**971, -2.**1023], -maxfloat),
747-
([-2.**1023, -2.**1023, 2.**970], OverflowError),
748-
([-2.**1023, -2.**1023, 2.**970, 2.**-1074], OverflowError),
749-
([-2.**-1074, -2.**1023, -2.**1023, 2.**970], OverflowError),
750-
([2.**930, -2.**980, 2.**1023, 2.**1023, twopow, -twopow],
751-
OverflowError),
752-
([2.**1023, 2.**1023, -1e307], OverflowError),
753-
([1e16, 1., 1e-16], 10000000000000002.0),
754-
([1e16-2., 1.-2.**-53, -(1e16-2.), -(1.-2.**-53)], 0.0),
755-
]
756-
757-
for i, (vals, s) in enumerate(test_values):
758-
if isinstance(s, type) and issubclass(s, Exception):
759-
try:
760-
m = math.sum(vals)
761-
except s:
762-
pass
763-
else:
764-
self.fail("test %d failed: got %r, expected %r "
765-
"for math.sum(%.100r)" %
766-
(i, m, s.__name__, vals))
767-
else:
768-
try:
769-
self.assertEqual(math.sum(vals), s)
770-
except OverflowError:
771-
self.fail("test %d failed: got OverflowError, expected %r "
772-
"for math.sum(%.100r)" % (i, s, vals))
773-
except ValueError:
774-
self.fail("test %d failed: got ValueError, expected %r "
775-
"for math.sum(%.100r)" % (i, s, vals))
776-
777-
# compare with output of msum above, but only when
778-
# result isn't an IEEE special or an exception
779-
if not math.isinf(s) and not math.isnan(s):
780-
self.assertEqual(msum(vals), s)
781-
782-
from random import random, gauss, shuffle
783-
for j in range(1000):
784-
vals = [7, 1e100, -7, -1e100, -9e-20, 8e-20] * 10
785-
s = 0
786-
for i in range(200):
787-
v = gauss(0, random()) ** 7 - s
788-
s += v
789-
vals.append(v)
790-
shuffle(vals)
791-
792-
s = msum(vals)
793-
self.assertEqual(msum(vals), math.sum(vals))
794-
795-
796740
def testTan(self):
797741
self.assertRaises(TypeError, math.tan)
798742
self.ftest('tan(0)', math.tan(0), 0)

Lib/test/test_random.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import time
66
import pickle
77
import warnings
8-
from math import log, exp, sqrt, pi, sum as msum
8+
from math import log, exp, sqrt, pi, fsum as msum
99
from test import support
1010

1111
class TestBasicOps(unittest.TestCase):

0 commit comments

Comments
 (0)