Sum.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.numbers.core;

  18. import java.util.function.DoubleConsumer;
  19. import java.util.function.DoubleSupplier;

  20. /**
  21.  * Class providing accurate floating-point sums and linear combinations.
  22.  * This class uses techniques to mitigate round off errors resulting from
  23.  * standard floating-point operations, increasing the overall accuracy of
  24.  * results at the cost of a moderate increase in the number of computations.
  25.  * This functionality can be viewed as filling the gap between standard
  26.  * floating point operations (fast but prone to round off errors) and
  27.  * {@link java.math.BigDecimal} (perfectly accurate but slow).
  28.  *
  29.  * <p><strong>Usage</strong>
  30.  * <p>This class use a builder pattern in order to maximize the flexibility
  31.  * of the API. Typical use involves constructing an instance from one
  32.  * of the factory methods, adding any number of {@link #add(double) single value terms}
  33.  * and/or {@link #addProduct(double, double) products}, and then extracting the
  34.  * computed sum. Convenience methods exist for adding multiple values or products at once.
  35.  * The examples below demonstrate some simple use cases.
  36.  *
  37.  * <pre>
  38.  * // compute the sum a1 + a2 + a3 + a4
  39.  * Sum sum = Sum.of(a1);
  40.  *      .add(a2)
  41.  *      .add(a3)
  42.  *      .add(a4);
  43.  * double result = sum.getAsDouble();
  44.  *
  45.  * // same as above but using the varargs factory method
  46.  * double result = Sum.of(a1, a2, a3, a4).getAsDouble();
  47.  *
  48.  * // compute the dot product of two arrays of the same length, a and b
  49.  * Sum sum = Sum.create();
  50.  * for (int i = 0; i &lt; a.length; ++i) {
  51.  *      sum.addProduct(a[i], b[i]);
  52.  * }
  53.  * double result = sum.getAsDouble();
  54.  *
  55.  * // same as above but using a convenience factory method
  56.  * double result = Sum.ofProducts(a, b).getAsDouble();
  57.  * </pre>
  58.  *
  59.  * <p>It is worth noting that this class is designed to reduce floating point errors
  60.  * <em>across a sequence of operations</em> and not just a single add or multiply. The
  61.  * standard IEEE floating point operations already produce the most accurate results
  62.  * possible given two arguments and this class does not improve on them. Rather, it tracks
  63.  * the errors inherent with each operation and uses them to reduce the error of the overall
  64.  * result. Therefore, this class is only beneficial in cases involving 3 or more floating point
  65.  * operations. Code such as {@code Sum.of(a, b).getAsDouble()} and
  66.  * {@code Sum.create().addProduct(a, b).getAsDouble()} only adds overhead with no benefit.
  67.  *
  68.  * <p><strong>Implementation Notes</strong>
  69.  * <p>This class internally uses the <em>Sum2S</em> and <em>Dot2S</em> algorithms described in
  70.  * <a href="https://doi.org/10.1137/030601818">
  71.  * Accurate Sum and Dot Product</a> by Takeshi Ogita, Siegfried M. Rump,
  72.  * and Shin'ichi Oishi (<em>SIAM J. Sci. Comput</em>, 2005). These are compensated
  73.  * summation and multiplication algorithms chosen here for their good
  74.  * balance of precision and performance. Future releases may choose to use
  75.  * different algorithms.
  76.  *
  77.  * <p>Results follow the IEEE 754 rules for addition: For example, if any
  78.  * input value is {@link Double#NaN}, the result is {@link Double#NaN}.
  79.  *
  80.  * <p>Instances of this class are mutable and not safe for use by multiple
  81.  * threads.
  82.  */
  83. public final class Sum
  84.     implements DoubleSupplier,
  85.                DoubleConsumer {
  86.     /** Standard sum. */
  87.     private double sum;
  88.     /** Compensation value. */
  89.     private double comp;

  90.     /**
  91.      * Constructs a new instance with the given initial value.
  92.      *
  93.      * @param initialValue Initial value.
  94.      */
  95.     private Sum(final double initialValue) {
  96.         sum = initialValue;
  97.     }

  98.     /**
  99.      * Adds a single term to this sum.
  100.      *
  101.      * @param t Value to add.
  102.      * @return this instance.
  103.      */
  104.     public Sum add(final double t) {
  105.         final double newSum = sum + t;
  106.         comp += DD.twoSumLow(sum, t, newSum);
  107.         sum = newSum;

  108.         return this;
  109.     }

  110.     /**
  111.      * Adds values from the given array to the sum.
  112.      *
  113.      * @param terms Terms to add.
  114.      * @return this instance.
  115.      */
  116.     public Sum add(final double... terms) {
  117.         for (final double t : terms) {
  118.             add(t);
  119.         }

  120.         return this;
  121.     }

  122.     /**
  123.      * Adds the high-accuracy product \( a b \) to this sum.
  124.      *
  125.      * @param a Factor
  126.      * @param b Factor.
  127.      * @return this instance
  128.      */
  129.     public Sum addProduct(final double a,
  130.                           final double b) {
  131.         final double ab = a * b;
  132.         final double pLow = ExtendedPrecision.productLow(a, b, ab);

  133.         final double newSum = sum + ab;
  134.         comp += DD.twoSumLow(sum, ab, newSum) + pLow;
  135.         sum = newSum;

  136.         return this;
  137.     }

  138.     /**
  139.      * Adds \( \sum_i a_i b_i \) to this sum.
  140.      *
  141.      * @param a Factors.
  142.      * @param b Factors.
  143.      * @return this instance.
  144.      * @throws IllegalArgumentException if the arrays do not have the same length.
  145.      */
  146.     public Sum addProducts(final double[] a,
  147.                            final double[] b) {
  148.         final int len = a.length;
  149.         if (len != b.length) {
  150.             throw new IllegalArgumentException("Dimension mismatch: " +
  151.                                                a.length + " != " + b.length);
  152.         }

  153.         for (int i = 0; i < len; ++i) {
  154.             addProduct(a[i], b[i]);
  155.         }

  156.         return this;
  157.     }

  158.     /**
  159.      * Adds another sum to this sum.
  160.      *
  161.      * @param other Sum to add.
  162.      * @return this instance.
  163.      */
  164.     public Sum add(final Sum other) {
  165.         return add(other.sum, other.comp);
  166.     }

  167.     /**
  168.      * Subtracts another sum from this sum.
  169.      *
  170.      * @param other Sum to subtract.
  171.      * @return this instance.
  172.      * @since 1.2
  173.      */
  174.     public Sum subtract(final Sum other) {
  175.         return add(-other.sum, -other.comp);
  176.     }

  177.     /**
  178.      * Adds the sum and compensation to this sum.
  179.      *
  180.      * <p>This is a utility method to extract both values from a sum
  181.      * before addition to ensure there are no issues when adding a sum
  182.      * to itself.
  183.      *
  184.      * <p>This method sums the values in double-double precision.
  185.      * This enforces symmetry when combining sums and maximises the available precision.
  186.      *
  187.      * @param s Sum.
  188.      * @param c Compensation.
  189.      * @return this instance.
  190.      */
  191.     private Sum add(double s, double c) {
  192.         // Re-normalise the sums and combine in double-double precision.
  193.         // Note: The conversion to a DD is lossless.
  194.         final DD result = DD.ofSum(sum, comp).add(DD.ofSum(s, c));
  195.         if (result.isFinite()) {
  196.             sum = result.hi();
  197.             comp = result.lo();
  198.         } else {
  199.             // compensation can be NaN from accumulating one or more same-signed infinite values.
  200.             // Do not pollute the regular IEEE754 sum with a spurious NaN.
  201.             add(s);
  202.             if (!Double.isNaN(c)) {
  203.                 add(c);
  204.             }
  205.         }
  206.         return this;
  207.     }

  208.     /**
  209.      * Adds a single term to this sum.
  210.      * This is equivalent to {@link #add(double)}.
  211.      *
  212.      * @param value Value to add.
  213.      *
  214.      * @see #add(double)
  215.      */
  216.     @Override
  217.     public void accept(final double value) {
  218.         add(value);
  219.     }

  220.     /**
  221.      * Gets the sum value.
  222.      *
  223.      * @return the sum value.
  224.      */
  225.     @Override
  226.     public double getAsDouble() {
  227.         // High-precision value if it is finite, standard IEEE754 result otherwise.
  228.         final double hpsum = sum + comp;
  229.         return Double.isFinite(hpsum) ?
  230.                 hpsum :
  231.                 sum;
  232.     }

  233.     /**
  234.      * Creates a new instance with an initial value of zero.
  235.      *
  236.      * @return a new instance.
  237.      */
  238.     public static Sum create() {
  239.         return new Sum(0d);
  240.     }

  241.     /**
  242.      * Creates an instance initialized to the given value.
  243.      *
  244.      * @param a Initial value.
  245.      * @return a new instance.
  246.      */
  247.     public static Sum of(final double a) {
  248.         return new Sum(a);
  249.     }

  250.     /**
  251.      * Creates an instance containing the sum of the given values.
  252.      *
  253.      * @param values Values to add.
  254.      * @return a new instance.
  255.      */
  256.     public static Sum of(final double... values) {
  257.         return create().add(values);
  258.     }

  259.     /**
  260.      * Creates a new instance containing \( \sum_i a_i b_i \).
  261.      *
  262.      * @param a Factors.
  263.      * @param b Factors.
  264.      * @return a new instance.
  265.      */
  266.     public static Sum ofProducts(final double[] a,
  267.                                  final double[] b) {
  268.         return create().addProducts(a, b);
  269.     }
  270. }