I am +1 on the idea here and am wondering if we should think a little more radically even. Greg has - correctly, IMO - complained that the current structure of the linear package makes it awkward to implement optimized operations for some classes of matrices and operations. I a wonder whether it might not make sense to encapsulate high-performance, array-based implementations of core operations in utils classes that can be used like the methods below. I remember back in the very early days of [math] suggesting this for statistical algorithms and being talked out of it in favor of a more classical OO approach. I wonder if by organizing things correctly, we might not be able to have the best of both worlds.
Phil On 8/5/11 8:01 AM, l...@apache.org wrote: > Author: luc > Date: Fri Aug 5 15:01:49 2011 > New Revision: 1154250 > > URL: http://svn.apache.org/viewvc?rev=1154250&view=rev > Log: > Added a few linearCombination utility methods in MathUtils to compute > accurately > linear combinations a1.b1 + a2.b2 + ... + an.bn taking great care to > compensate > for cancellation effects. This both improves and simplify several methods in > euclidean geometry classes, including linear constructors, dot product and > cross > product. > > Modified: > > commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java > commons/proper/math/trunk/src/site/xdoc/changes.xml > > Modified: > commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java > URL: > http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java?rev=1154250&r1=1154249&r2=1154250&view=diff > ============================================================================== > --- > commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java > (original) > +++ > commons/proper/math/trunk/src/main/java/org/apache/commons/math/util/MathUtils.java > Fri Aug 5 15:01:49 2011 > @@ -19,22 +19,22 @@ package org.apache.commons.math.util; > > import java.math.BigDecimal; > import java.math.BigInteger; > -import java.util.Arrays; > -import java.util.List; > import java.util.ArrayList; > -import java.util.Comparator; > +import java.util.Arrays; > import java.util.Collections; > +import java.util.Comparator; > +import java.util.List; > > -import org.apache.commons.math.exception.util.Localizable; > -import org.apache.commons.math.exception.util.LocalizedFormats; > -import org.apache.commons.math.exception.NonMonotonousSequenceException; > import org.apache.commons.math.exception.DimensionMismatchException; > -import org.apache.commons.math.exception.NullArgumentException; > -import org.apache.commons.math.exception.NotPositiveException; > import org.apache.commons.math.exception.MathArithmeticException; > import org.apache.commons.math.exception.MathIllegalArgumentException; > -import org.apache.commons.math.exception.NumberIsTooLargeException; > +import org.apache.commons.math.exception.NonMonotonousSequenceException; > import org.apache.commons.math.exception.NotFiniteNumberException; > +import org.apache.commons.math.exception.NotPositiveException; > +import org.apache.commons.math.exception.NullArgumentException; > +import org.apache.commons.math.exception.NumberIsTooLargeException; > +import org.apache.commons.math.exception.util.Localizable; > +import org.apache.commons.math.exception.util.LocalizedFormats; > > /** > * Some useful additions to the built-in functions in {@link Math}. > @@ -91,6 +91,9 @@ public final class MathUtils { > 1307674368000l, 20922789888000l, 355687428096000l, > 6402373705728000l, 121645100408832000l, 2432902008176640000l }; > > + /** Factor used for splitting double numbers: n = 2^27 + 1. */ > + private static final int SPLIT_FACTOR = 0x8000001; > + > /** > * Private Constructor > */ > @@ -2332,4 +2335,277 @@ public final class MathUtils { > throw new NullArgumentException(); > } > } > + > + /** > + * Compute a linear combination accurately. > + * <p> > + * This method computes a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> 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 <a > + * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547"> > + * Accurate Sum and Dot Product</a> by Takeshi Ogita, > + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. > Comput. > + * </p> > + * @param a1 first factor of the first term > + * @param b1 second factor of the first term > + * @param a2 first factor of the second term > + * @param b2 second factor of the second term > + * @return a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> > + * @see #linearCombination(double, double, double, double, double, > double) > + * @see #linearCombination(double, double, double, double, double, > double, double, double) > + */ > + public static double linearCombination(final double a1, final double b1, > + final double a2, final double b2) > { > + > + // the code below is split in many additions/subtractions that may > + // appear redundant. However, they shoud NOT be simplified, as they > + // do use IEEE753 floating point arithmetic rouding properties. > + // as an example, the expression "ca1 - (ca1 - a1)" is NOT the same > as "a1" > + // The variables naming conventions are that xyzHigh contains the > most significant > + // bits of xyz and xyzLow contains its least significant bits. So > theoretically > + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this > sum cannot > + // be represented in only one double precision number so we preserve > two numbers > + // to hold it as long as we can, combining the high and low order > bits together > + // only at the end, after cancellation may have occurred on high > order bits > + > + // split a1 and b1 as two 26 bits numbers > + final double ca1 = SPLIT_FACTOR * a1; > + final double a1High = ca1 - (ca1 - a1); > + final double a1Low = a1 - a1High; > + final double cb1 = SPLIT_FACTOR * b1; > + final double b1High = cb1 - (cb1 - b1); > + final double b1Low = b1 - b1High; > + > + // accurate multiplication a1 * b1 > + final double prod1High = a1 * b1; > + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * > b1High) - a1Low * b1High) - a1High * b1Low); > + > + // split a2 and b2 as two 26 bits numbers > + final double ca2 = SPLIT_FACTOR * a2; > + final double a2High = ca2 - (ca2 - a2); > + final double a2Low = a2 - a2High; > + final double cb2 = SPLIT_FACTOR * b2; > + final double b2High = cb2 - (cb2 - b2); > + final double b2Low = b2 - b2High; > + > + // accurate multiplication a2 * b2 > + final double prod2High = a2 * b2; > + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * > b2High) - a2Low * b2High) - a2High * b2Low); > + > + // accurate addition a1 * b1 + a2 * b2 > + final double s12High = prod1High + prod2High; > + final double s12Prime = s12High - prod2High; > + final double s12Low = (prod2High - (s12High - s12Prime)) + > (prod1High - s12Prime); > + > + // final rounding, s12 may have suffered many cancellations, we try > + // to recover some bits from the extra words we have saved up to now > + return s12High + (prod1Low + prod2Low + s12Low); > + > + } > + > + /** > + * Compute a linear combination accurately. > + * <p> > + * This method computes a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> + a<sub>3</sub>×b<sub>3</sub> > + * 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 <a > + * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547"> > + * Accurate Sum and Dot Product</a> by Takeshi Ogita, > + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. > Comput. > + * </p> > + * @param a1 first factor of the first term > + * @param b1 second factor of the first term > + * @param a2 first factor of the second term > + * @param b2 second factor of the second term > + * @param a3 first factor of the third term > + * @param b3 second factor of the third term > + * @return a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> + a<sub>3</sub>×b<sub>3</sub> > + * @see #linearCombination(double, double, double, double) > + * @see #linearCombination(double, double, double, double, double, > double, double, double) > + */ > + public static double linearCombination(final double a1, final double b1, > + final double a2, final double b2, > + final double a3, final double b3) > { > + > + // the code below is split in many additions/subtractions that may > + // appear redundant. However, they shoud NOT be simplified, as they > + // do use IEEE753 floating point arithmetic rouding properties. > + // as an example, the expression "ca1 - (ca1 - a1)" is NOT the same > as "a1" > + // The variables naming conventions are that xyzHigh contains the > most significant > + // bits of xyz and xyzLow contains its least significant bits. So > theoretically > + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this > sum cannot > + // be represented in only one double precision number so we preserve > two numbers > + // to hold it as long as we can, combining the high and low order > bits together > + // only at the end, after cancellation may have occurred on high > order bits > + > + // split a1 and b1 as two 26 bits numbers > + final double ca1 = SPLIT_FACTOR * a1; > + final double a1High = ca1 - (ca1 - a1); > + final double a1Low = a1 - a1High; > + final double cb1 = SPLIT_FACTOR * b1; > + final double b1High = cb1 - (cb1 - b1); > + final double b1Low = b1 - b1High; > + > + // accurate multiplication a1 * b1 > + final double prod1High = a1 * b1; > + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * > b1High) - a1Low * b1High) - a1High * b1Low); > + > + // split a2 and b2 as two 26 bits numbers > + final double ca2 = SPLIT_FACTOR * a2; > + final double a2High = ca2 - (ca2 - a2); > + final double a2Low = a2 - a2High; > + final double cb2 = SPLIT_FACTOR * b2; > + final double b2High = cb2 - (cb2 - b2); > + final double b2Low = b2 - b2High; > + > + // accurate multiplication a2 * b2 > + final double prod2High = a2 * b2; > + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * > b2High) - a2Low * b2High) - a2High * b2Low); > + > + // split a3 and b3 as two 26 bits numbers > + final double ca3 = SPLIT_FACTOR * a3; > + final double a3High = ca3 - (ca3 - a3); > + final double a3Low = a3 - a3High; > + final double cb3 = SPLIT_FACTOR * b3; > + final double b3High = cb3 - (cb3 - b3); > + final double b3Low = b3 - b3High; > + > + // accurate multiplication a3 * b3 > + final double prod3High = a3 * b3; > + final double prod3Low = a3Low * b3Low - (((prod3High - a3High * > b3High) - a3Low * b3High) - a3High * b3Low); > + > + // accurate addition a1 * b1 + a2 * b2 > + final double s12High = prod1High + prod2High; > + final double s12Prime = s12High - prod2High; > + final double s12Low = (prod2High - (s12High - s12Prime)) + > (prod1High - s12Prime); > + > + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 > + final double s123High = s12High + prod3High; > + final double s123Prime = s123High - prod3High; > + final double s123Low = (prod3High - (s123High - s123Prime)) + > (s12High - s123Prime); > + > + // final rounding, s123 may have suffered many cancellations, we try > + // to recover some bits from the extra words we have saved up to now > + return s123High + (prod1Low + prod2Low + prod3Low + s12Low + > s123Low); > + > + } > + > + /** > + * Compute a linear combination accurately. > + * <p> > + * This method computes a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> + a<sub>3</sub>×b<sub>3</sub> > + > + * a<sub>4</sub>×b<sub>4</sub> > + * 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 <a > + * href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.2.1547"> > + * Accurate Sum and Dot Product</a> by Takeshi Ogita, > + * Siegfried M. Rump, and Shin'ichi Oishi published in SIAM J. Sci. > Comput. > + * </p> > + * @param a1 first factor of the first term > + * @param b1 second factor of the first term > + * @param a2 first factor of the second term > + * @param b2 second factor of the second term > + * @param a3 first factor of the third term > + * @param b3 second factor of the third term > + * @param a4 first factor of the third term > + * @param b4 second factor of the third term > + * @return a<sub>1</sub>×b<sub>1</sub> + > + * a<sub>2</sub>×b<sub>2</sub> + a<sub>3</sub>×b<sub>3</sub> > + > + * a<sub>4</sub>×b<sub>4</sub> > + * @see #linearCombination(double, double, double, double) > + * @see #linearCombination(double, double, double, double, double, > double) > + */ > + public static double linearCombination(final double a1, final double b1, > + final double a2, final double b2, > + final double a3, final double b3, > + final double a4, final double b4) > { > + > + // the code below is split in many additions/subtractions that may > + // appear redundant. However, they shoud NOT be simplified, as they > + // do use IEEE753 floating point arithmetic rouding properties. > + // as an example, the expression "ca1 - (ca1 - a1)" is NOT the same > as "a1" > + // The variables naming conventions are that xyzHigh contains the > most significant > + // bits of xyz and xyzLow contains its least significant bits. So > theoretically > + // xyz is the sum xyzHigh + xyzLow, but in many cases below, this > sum cannot > + // be represented in only one double precision number so we preserve > two numbers > + // to hold it as long as we can, combining the high and low order > bits together > + // only at the end, after cancellation may have occurred on high > order bits > + > + // split a1 and b1 as two 26 bits numbers > + final double ca1 = SPLIT_FACTOR * a1; > + final double a1High = ca1 - (ca1 - a1); > + final double a1Low = a1 - a1High; > + final double cb1 = SPLIT_FACTOR * b1; > + final double b1High = cb1 - (cb1 - b1); > + final double b1Low = b1 - b1High; > + > + // accurate multiplication a1 * b1 > + final double prod1High = a1 * b1; > + final double prod1Low = a1Low * b1Low - (((prod1High - a1High * > b1High) - a1Low * b1High) - a1High * b1Low); > + > + // split a2 and b2 as two 26 bits numbers > + final double ca2 = SPLIT_FACTOR * a2; > + final double a2High = ca2 - (ca2 - a2); > + final double a2Low = a2 - a2High; > + final double cb2 = SPLIT_FACTOR * b2; > + final double b2High = cb2 - (cb2 - b2); > + final double b2Low = b2 - b2High; > + > + // accurate multiplication a2 * b2 > + final double prod2High = a2 * b2; > + final double prod2Low = a2Low * b2Low - (((prod2High - a2High * > b2High) - a2Low * b2High) - a2High * b2Low); > + > + // split a3 and b3 as two 26 bits numbers > + final double ca3 = SPLIT_FACTOR * a3; > + final double a3High = ca3 - (ca3 - a3); > + final double a3Low = a3 - a3High; > + final double cb3 = SPLIT_FACTOR * b3; > + final double b3High = cb3 - (cb3 - b3); > + final double b3Low = b3 - b3High; > + > + // accurate multiplication a3 * b3 > + final double prod3High = a3 * b3; > + final double prod3Low = a3Low * b3Low - (((prod3High - a3High * > b3High) - a3Low * b3High) - a3High * b3Low); > + > + // split a4 and b4 as two 26 bits numbers > + final double ca4 = SPLIT_FACTOR * a4; > + final double a4High = ca4 - (ca4 - a4); > + final double a4Low = a4 - a4High; > + final double cb4 = SPLIT_FACTOR * b4; > + final double b4High = cb4 - (cb4 - b4); > + final double b4Low = b4 - b4High; > + > + // accurate multiplication a4 * b4 > + final double prod4High = a4 * b4; > + final double prod4Low = a4Low * b4Low - (((prod4High - a4High * > b4High) - a4Low * b4High) - a4High * b4Low); > + > + // accurate addition a1 * b1 + a2 * b2 > + final double s12High = prod1High + prod2High; > + final double s12Prime = s12High - prod2High; > + final double s12Low = (prod2High - (s12High - s12Prime)) + > (prod1High - s12Prime); > + > + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 > + final double s123High = s12High + prod3High; > + final double s123Prime = s123High - prod3High; > + final double s123Low = (prod3High - (s123High - s123Prime)) + > (s12High - s123Prime); > + > + // accurate addition a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4 > + final double s1234High = s123High + prod4High; > + final double s1234Prime = s1234High - prod4High; > + final double s1234Low = (prod4High - (s1234High - s1234Prime)) + > (s123High - s1234Prime); > + > + // final rounding, s1234 may have suffered many cancellations, we try > + // to recover some bits from the extra words we have saved up to now > + return s1234High + (prod1Low + prod2Low + prod3Low + prod4Low + > s12Low + s123Low + s1234Low); > + > + } > + > } > > Modified: commons/proper/math/trunk/src/site/xdoc/changes.xml > URL: > http://svn.apache.org/viewvc/commons/proper/math/trunk/src/site/xdoc/changes.xml?rev=1154250&r1=1154249&r2=1154250&view=diff > ============================================================================== > --- commons/proper/math/trunk/src/site/xdoc/changes.xml (original) > +++ commons/proper/math/trunk/src/site/xdoc/changes.xml Fri Aug 5 15:01:49 > 2011 > @@ -52,6 +52,13 @@ The <action> type attribute can be add,u > If the output is not quite correct, check for invisible trailing spaces! > --> > <release version="3.0" date="TBD" description="TBD"> > + <action dev="luc" type="add" > > + Added a few linearCombination utility methods in MathUtils to > compute accurately > + linear combinations a1.b1 + a2.b2 + ... + an.bn taking great care to > compensate > + for cancellation effects. This both improves and simplify several > methods in > + euclidean geometry classes, including linear constructors, dot > product and cross > + product. > + </action> > <action dev="psteitz" type="fix" issue="MATH-640"> > Fixed bugs in AbstractRandomGenerator nextInt() and nextLong() > default > implementations. Prior to the fix for this issue, these methods > > > --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org For additional commands, e-mail: dev-h...@commons.apache.org