mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	GH-100485: Add extended accuracy test. Switch to faster fma() based variant. GH-101383)
This commit is contained in:
		
							parent
							
								
									db757f0e44
								
							
						
					
					
						commit
						84483aacc0
					
				
					 2 changed files with 100 additions and 36 deletions
				
			
		| 
						 | 
					@ -1369,6 +1369,89 @@ def run(func, *args):
 | 
				
			||||||
                            args,
 | 
					                            args,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @requires_IEEE_754
 | 
				
			||||||
 | 
					    @unittest.skipIf(HAVE_DOUBLE_ROUNDING,
 | 
				
			||||||
 | 
					                         "sumprod() accuracy not guaranteed on machines with double rounding")
 | 
				
			||||||
 | 
					    @support.cpython_only    # Other implementations may choose a different algorithm
 | 
				
			||||||
 | 
					    @support.requires_resource('cpu')
 | 
				
			||||||
 | 
					    def test_sumprod_extended_precision_accuracy(self):
 | 
				
			||||||
 | 
					        import operator
 | 
				
			||||||
 | 
					        from fractions import Fraction
 | 
				
			||||||
 | 
					        from itertools import starmap
 | 
				
			||||||
 | 
					        from collections import namedtuple
 | 
				
			||||||
 | 
					        from math import log2, exp2, fabs
 | 
				
			||||||
 | 
					        from random import choices, uniform, shuffle
 | 
				
			||||||
 | 
					        from statistics import median
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DotExample = namedtuple('DotExample', ('x', 'y', 'target_sumprod', 'condition'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def DotExact(x, y):
 | 
				
			||||||
 | 
					            vec1 = map(Fraction, x)
 | 
				
			||||||
 | 
					            vec2 = map(Fraction, y)
 | 
				
			||||||
 | 
					            return sum(starmap(operator.mul, zip(vec1, vec2, strict=True)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def Condition(x, y):
 | 
				
			||||||
 | 
					            return 2.0 * DotExact(map(abs, x), map(abs, y)) / abs(DotExact(x, y))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def linspace(lo, hi, n):
 | 
				
			||||||
 | 
					            width = (hi - lo) / (n - 1)
 | 
				
			||||||
 | 
					            return [lo + width * i for i in range(n)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def GenDot(n, c):
 | 
				
			||||||
 | 
					            """ Algorithm 6.1 (GenDot) works as follows. The condition number (5.7) of
 | 
				
			||||||
 | 
					            the dot product xT y is proportional to the degree of cancellation. In
 | 
				
			||||||
 | 
					            order to achieve a prescribed cancellation, we generate the first half of
 | 
				
			||||||
 | 
					            the vectors x and y randomly within a large exponent range. This range is
 | 
				
			||||||
 | 
					            chosen according to the anticipated condition number. The second half of x
 | 
				
			||||||
 | 
					            and y is then constructed choosing xi randomly with decreasing exponent,
 | 
				
			||||||
 | 
					            and calculating yi such that some cancellation occurs. Finally, we permute
 | 
				
			||||||
 | 
					            the vectors x, y randomly and calculate the achieved condition number.
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert n >= 6
 | 
				
			||||||
 | 
					            n2 = n // 2
 | 
				
			||||||
 | 
					            x = [0.0] * n
 | 
				
			||||||
 | 
					            y = [0.0] * n
 | 
				
			||||||
 | 
					            b = log2(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # First half with exponents from 0 to |_b/2_| and random ints in between
 | 
				
			||||||
 | 
					            e = choices(range(int(b/2)), k=n2)
 | 
				
			||||||
 | 
					            e[0] = int(b / 2) + 1
 | 
				
			||||||
 | 
					            e[-1] = 0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            x[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
 | 
				
			||||||
 | 
					            y[:n2] = [uniform(-1.0, 1.0) * exp2(p) for p in e]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Second half
 | 
				
			||||||
 | 
					            e = list(map(round, linspace(b/2, 0.0 , n-n2)))
 | 
				
			||||||
 | 
					            for i in range(n2, n):
 | 
				
			||||||
 | 
					                x[i] = uniform(-1.0, 1.0) * exp2(e[i - n2])
 | 
				
			||||||
 | 
					                y[i] = (uniform(-1.0, 1.0) * exp2(e[i - n2]) - DotExact(x, y)) / x[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Shuffle
 | 
				
			||||||
 | 
					            pairs = list(zip(x, y))
 | 
				
			||||||
 | 
					            shuffle(pairs)
 | 
				
			||||||
 | 
					            x, y = zip(*pairs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return DotExample(x, y, DotExact(x, y), Condition(x, y))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def RelativeError(res, ex):
 | 
				
			||||||
 | 
					            x, y, target_sumprod, condition = ex
 | 
				
			||||||
 | 
					            n = DotExact(list(x) + [-res], list(y) + [1])
 | 
				
			||||||
 | 
					            return fabs(n / target_sumprod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def Trial(dotfunc, c, n):
 | 
				
			||||||
 | 
					            ex = GenDot(10, c)
 | 
				
			||||||
 | 
					            res = dotfunc(ex.x, ex.y)
 | 
				
			||||||
 | 
					            return RelativeError(res, ex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        times = 1000          # Number of trials
 | 
				
			||||||
 | 
					        n = 20                # Length of vectors
 | 
				
			||||||
 | 
					        c = 1e30              # Target condition number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        relative_err = median(Trial(math.sumprod, c, n) for i in range(times))
 | 
				
			||||||
 | 
					        self.assertLess(relative_err, 1e-16)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def testModf(self):
 | 
					    def testModf(self):
 | 
				
			||||||
        self.assertRaises(TypeError, math.modf)
 | 
					        self.assertRaises(TypeError, math.modf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2832,12 +2832,7 @@ long_add_would_overflow(long a, long b)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
Double and triple length extended precision floating point arithmetic
 | 
					Double and triple length extended precision algorithms from:
 | 
				
			||||||
based on:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  A Floating-Point Technique for Extending the Available Precision
 | 
					 | 
				
			||||||
  by T. J. Dekker
 | 
					 | 
				
			||||||
  https://csclub.uwaterloo.ca/~pbarfuss/dekker1971.pdf
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Accurate Sum and Dot Product
 | 
					  Accurate Sum and Dot Product
 | 
				
			||||||
  by Takeshi Ogita, Siegfried M. Rump, and Shin’Ichi Oishi
 | 
					  by Takeshi Ogita, Siegfried M. Rump, and Shin’Ichi Oishi
 | 
				
			||||||
| 
						 | 
					@ -2848,36 +2843,22 @@ based on:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct{ double hi; double lo; } DoubleLength;
 | 
					typedef struct{ double hi; double lo; } DoubleLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline DoubleLength
 | 
					static DoubleLength
 | 
				
			||||||
twosum(double a, double b)
 | 
					dl_sum(double a, double b)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // Rump Algorithm 3.1 Error-free transformation of the sum
 | 
					    /* Algorithm 3.1 Error-free transformation of the sum */
 | 
				
			||||||
    double x = a + b;
 | 
					    double x = a + b;
 | 
				
			||||||
    double z = x - a;
 | 
					    double z = x - a;
 | 
				
			||||||
    double y = (a - (x - z)) + (b - z);
 | 
					    double y = (a - (x - z)) + (b - z);
 | 
				
			||||||
    return (DoubleLength) {x, y};
 | 
					    return (DoubleLength) {x, y};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline DoubleLength
 | 
					static DoubleLength
 | 
				
			||||||
dl_split(double x) {
 | 
					 | 
				
			||||||
    // Rump Algorithm 3.2 Error-free splitting of a floating point number
 | 
					 | 
				
			||||||
    // Dekker (5.5) and (5.6).
 | 
					 | 
				
			||||||
    double t = x * 134217729.0;  // Veltkamp constant = 2.0 ** 27 + 1
 | 
					 | 
				
			||||||
    double hi = t - (t - x);
 | 
					 | 
				
			||||||
    double lo = x - hi;
 | 
					 | 
				
			||||||
    return (DoubleLength) {hi, lo};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static inline DoubleLength
 | 
					 | 
				
			||||||
dl_mul(double x, double y)
 | 
					dl_mul(double x, double y)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // Dekker (5.12) and mul12()
 | 
					    /* Algorithm 3.5. Error-free transformation of a product */
 | 
				
			||||||
    DoubleLength xx = dl_split(x);
 | 
					    double z = x * y;
 | 
				
			||||||
    DoubleLength yy = dl_split(y);
 | 
					    double zz = fma(x, y, -z);
 | 
				
			||||||
    double p = xx.hi * yy.hi;
 | 
					 | 
				
			||||||
    double q = xx.hi * yy.lo + xx.lo * yy.hi;
 | 
					 | 
				
			||||||
    double z = p + q;
 | 
					 | 
				
			||||||
    double zz = p - z + q + xx.lo * yy.lo;
 | 
					 | 
				
			||||||
    return (DoubleLength) {z, zz};
 | 
					    return (DoubleLength) {z, zz};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2885,21 +2866,21 @@ typedef struct { double hi; double lo; double tiny; } TripleLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static const TripleLength tl_zero = {0.0, 0.0, 0.0};
 | 
					static const TripleLength tl_zero = {0.0, 0.0, 0.0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline TripleLength
 | 
					static TripleLength
 | 
				
			||||||
tl_fma(TripleLength total, double x, double y)
 | 
					tl_fma(double x, double y, TripleLength total)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // Rump Algorithm 5.10 with K=3 and using SumKVert
 | 
					    /* Algorithm 5.10 with SumKVert for K=3 */
 | 
				
			||||||
    DoubleLength pr = dl_mul(x, y);
 | 
					    DoubleLength pr = dl_mul(x, y);
 | 
				
			||||||
    DoubleLength sm = twosum(total.hi, pr.hi);
 | 
					    DoubleLength sm = dl_sum(total.hi, pr.hi);
 | 
				
			||||||
    DoubleLength r1 = twosum(total.lo, pr.lo);
 | 
					    DoubleLength r1 = dl_sum(total.lo, pr.lo);
 | 
				
			||||||
    DoubleLength r2 = twosum(r1.hi, sm.lo);
 | 
					    DoubleLength r2 = dl_sum(r1.hi, sm.lo);
 | 
				
			||||||
    return (TripleLength) {sm.hi, r2.hi, total.tiny + r1.lo + r2.lo};
 | 
					    return (TripleLength) {sm.hi, r2.hi, total.tiny + r1.lo + r2.lo};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline double
 | 
					static double
 | 
				
			||||||
tl_to_d(TripleLength total)
 | 
					tl_to_d(TripleLength total)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    DoubleLength last = twosum(total.lo, total.hi);
 | 
					    DoubleLength last = dl_sum(total.lo, total.hi);
 | 
				
			||||||
    return total.tiny + last.lo + last.hi;
 | 
					    return total.tiny + last.lo + last.hi;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3066,7 +3047,7 @@ math_sumprod_impl(PyObject *module, PyObject *p, PyObject *q)
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    goto finalize_flt_path;
 | 
					                    goto finalize_flt_path;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                TripleLength new_flt_total = tl_fma(flt_total, flt_p, flt_q);
 | 
					                TripleLength new_flt_total = tl_fma(flt_p, flt_q, flt_total);
 | 
				
			||||||
                if (isfinite(new_flt_total.hi)) {
 | 
					                if (isfinite(new_flt_total.hi)) {
 | 
				
			||||||
                    flt_total = new_flt_total;
 | 
					                    flt_total = new_flt_total;
 | 
				
			||||||
                    flt_total_in_use = true;
 | 
					                    flt_total_in_use = true;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue