diff --git a/src/main/java/org/apache/commons/math/util/MathUtils.java b/src/main/java/org/apache/commons/math/util/MathUtils.java index 5f0f5bfdc..e54b04c5a 100644 --- a/src/main/java/org/apache/commons/math/util/MathUtils.java +++ b/src/main/java/org/apache/commons/math/util/MathUtils.java @@ -2608,4 +2608,64 @@ public final class MathUtils { } + /** + * Compute a linear combination accurately. + * This method computes the sum of the products + * ai bi to high accuracy. + * It does so by using specific multiplication and addition algorithms to + * preserve accuracy and reduce cancellation effects. + *
+ * It is based on the 2005 paper + * + * Accurate Sum and Dot Product by Takeshi Ogita, Siegfried M. Rump, + * and Shin'ichi Oishi published in SIAM J. Sci. Comput. + * + * @param a Factors. + * @param b Factors. + * @return Σi ai bi. + */ + public static double linearCombination(final double[] a, final double[] b) { + final int len = a.length; + if (len != b.length) { + throw new DimensionMismatchException(len, b.length); + } + + final double[] prodHigh = new double[len]; + double prodLowSum = 0; + + for (int i = 0; i < len; i++) { + final double ai = a[i]; + final double ca = SPLIT_FACTOR * ai; + final double aHigh = ca - (ca - ai); + final double aLow = ai - aHigh; + + final double bi = b[i]; + final double cb = SPLIT_FACTOR * bi; + final double bHigh = cb - (cb - bi); + final double bLow = bi - bHigh; + prodHigh[i] = ai * bi; + final double prodLow = aLow * bLow - (((prodHigh[i] - + aHigh * bHigh) - + aLow * bHigh) - + aHigh * bLow); + prodLowSum += prodLow; + } + + final int lenMinusOne = len - 1; + final double[] sHigh = new double[lenMinusOne]; + + sHigh[0] = prodHigh[0] + prodHigh[1]; + double sPrime = sHigh[0] - prodHigh[1]; + double sLowSum = (prodHigh[1] - (sHigh[0] - sPrime)) + (prodHigh[0] - sPrime); + + for (int i = 1; i < lenMinusOne; i++) { + final int prev = i - 1; + final int next = i + 1; + sHigh[i] = sHigh[prev] + prodHigh[next]; + sPrime = sHigh[i] - prodHigh[next]; + sLowSum += (prodHigh[next] - (sHigh[i] - sPrime)) + (sHigh[prev] - sPrime); + } + + return sHigh[lenMinusOne - 1] + (prodLowSum + sLowSum); + } } diff --git a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java index 7ca654c68..cd5971291 100644 --- a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java +++ b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java @@ -1841,4 +1841,25 @@ public final class MathUtilsTest { // Expected. } } + + @Test + public void testLinearCombination1() { + final double[] a = new double[] { + -1321008684645961.0 / 268435456.0, + -5774608829631843.0 / 268435456.0, + -7645843051051357.0 / 8589934592.0 + }; + final double[] b = new double[] { + -5712344449280879.0 / 2097152.0, + -4550117129121957.0 / 2097152.0, + 8846951984510141.0 / 131072.0 + }; + + final double abSumInline = MathUtils.linearCombination(a[0], b[0], + a[1], b[1], + a[2], b[2]); + final double abSumArray = MathUtils.linearCombination(a, b); + + Assert.assertEquals(abSumInline, abSumArray, 0); + } }