@@ -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 )
0 commit comments