mirror of https://github.com/python/cpython
GH-100485: Add extended accuracy test. Switch to faster fma() based variant. GH-101383)
This commit is contained in:
parent
db757f0e44
commit
84483aacc0
|
@ -1369,6 +1369,89 @@ class MathTests(unittest.TestCase):
|
||||||
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…
Reference in New Issue