This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 496740c6be6925c173c8860982ec9de238c40f2f
Author: Alex Herbert <aherb...@apache.org>
AuthorDate: Thu Jan 19 12:51:24 2023 +0000

    NUMBERS-192: Add subtract method to Sum
    
    Updated user guide with subtract example and more usage details.
---
 .../java/org/apache/commons/numbers/core/Sum.java  | 28 ++++++++++++++++++---
 .../org/apache/commons/numbers/core/SumTest.java   | 29 ++++++++++++++++++++++
 .../apache/commons/numbers/core/UserGuideTest.java | 11 ++++++++
 src/changes/changes.xml                            |  3 +++
 src/site/apt/userguide/index.apt                   | 25 +++++++++++++++++--
 5 files changed, 90 insertions(+), 6 deletions(-)

diff --git 
a/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/Sum.java 
b/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/Sum.java
index cc5e7791..8953efd1 100644
--- 
a/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/Sum.java
+++ 
b/commons-numbers-core/src/main/java/org/apache/commons/numbers/core/Sum.java
@@ -177,11 +177,31 @@ public final class Sum
      * @return this instance.
      */
     public Sum add(final Sum other) {
-        // Pull both values first to ensure there are
-        // no issues when adding a sum to itself.
-        final double s = other.sum;
-        final double c = other.comp;
+        return add(other.sum, other.comp);
+    }
+
+    /**
+     * Subtracts another sum from this sum.
+     *
+     * @param other Sum to subtract.
+     * @return this instance.
+     */
+    public Sum subtract(final Sum other) {
+        return add(-other.sum, -other.comp);
+    }
 
+    /**
+     * Adds the sum and compensation to this sum.
+     *
+     * <p>This is a utility method to extract both values from a sum
+     * before addition to ensure there are no issues when adding a sum
+     * to itself.
+     *
+     * @param s Sum.
+     * @param c Compensation.
+     * @return this instance.
+     */
+    private Sum add(double s, double c) {
         return add(s).add(c);
     }
 
diff --git 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/SumTest.java
 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/SumTest.java
index f358ba36..5e42a727 100644
--- 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/SumTest.java
+++ 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/SumTest.java
@@ -102,6 +102,35 @@ class SumTest {
         Assertions.assertEquals(exactSum(a, b, a, b), s.add(s).getAsDouble());
     }
 
+    @Test
+    void testSubtract_sumInstance() {
+        // arrange
+        final double a = Math.PI;
+        final double b = Math.scalb(a, -53);
+        final double c = Math.scalb(a, -53);
+        final double d = Math.scalb(a, -27);
+        final double e = Math.scalb(a, -27);
+        final double f = Math.scalb(a, -50);
+
+        // act/assert
+        // add(-sum) == subtract(sum)
+        Assertions.assertEquals(
+                Sum.of(a, b, c, d).add(Sum.create()).getAsDouble(),
+                Sum.of(a, b, c, d).subtract(Sum.create()).getAsDouble());
+        Assertions.assertEquals(
+                Sum.of(a, b, c, d).add(Sum.of(-e, -f)).getAsDouble(),
+                Sum.of(a, b, c, d).subtract(Sum.of(e, f)).getAsDouble());
+        Assertions.assertEquals(
+                Sum.of(a, b).add(Sum.of(-a, -c).add(Sum.of(-d, -e, 
-f))).getAsDouble(),
+                Sum.of(a, b).subtract(Sum.of(a, c)).subtract(Sum.of(d, e, 
f)).getAsDouble());
+        Assertions.assertEquals(
+                Sum.of(a, b).add(Sum.of(-a, c).add(Sum.of(-d, e, 
-f))).getAsDouble(),
+                Sum.of(a, b).subtract(Sum.of(a, -c)).subtract(Sum.of(d, -e, 
f)).getAsDouble());
+
+        final Sum s = Sum.of(a, b);
+        Assertions.assertEquals(0, s.subtract(s).getAsDouble());
+    }
+
     @Test
     void testSumOfProducts_dimensionMismatch() {
         // act/assert
diff --git 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/UserGuideTest.java
 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/UserGuideTest.java
index 537c1a92..10f4d6e2 100644
--- 
a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/UserGuideTest.java
+++ 
b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/UserGuideTest.java
@@ -64,6 +64,17 @@ class UserGuideTest {
         Assertions.assertEquals(-1.0, x2);
     }
 
+    @Test
+    void testSum3() {
+        double x1 = 1e100 + 1 - 2 - 1e100;
+        Sum s1 = Sum.of(1e100, 1);
+        Sum s2 = Sum.of(2, 1e100);
+        double x2 = s1.subtract(s2).getAsDouble();
+        Assertions.assertEquals(0.0, x1);
+        Assertions.assertEquals(-1.0, x2);
+    }
+
+
     @Test
     void testPrecision1() {
         // Default allows no numbers between
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 569fdc6b..914b4af3 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -56,6 +56,9 @@ If the output is not quite correct, check for invisible 
trailing spaces!
     <release version="1.2" date="TBD" description="
 New features, updates and bug fixes.
 ">
+      <action dev="aherbert" type="add" issue="NUMBERS-192">
+        "Sum": Add subtract(Sum) method.
+      </action>
       <action dev="aherbert" type="add" issue="NUMBERS-191">
         "Stirling": Compute Stirling numbers of the first kind.
       </action>
diff --git a/src/site/apt/userguide/index.apt b/src/site/apt/userguide/index.apt
index f140f894..8cf82a4d 100644
--- a/src/site/apt/userguide/index.apt
+++ b/src/site/apt/userguide/index.apt
@@ -411,7 +411,7 @@ Sum.of(1, 2, Double.NaN).getAsDouble()                 // 
NaN
 Sum.of(1, 2, Double.NEGATIVE_INFINITY).getAsDouble()   // -inf
 +------------------------------------------+
 
-  The implementation provides an effective 106 bit floating point significand. 
However the
+  The implementation provides up to a 106 bit floating point significand. 
However the
   significand is split into two <<<double>>> values which may be separated by 
more than <<<2^53>>>
   by using the exponent of the second <<<double>>>.
   This provides protection against cancellation in situations that would not 
be handled by an
@@ -422,14 +422,35 @@ double x1 = 1e100 + 1 - 2 - 1e100;
 double x2 = Sum.of(1e100, 1, -2, -1e100).getAsDouble();
 // x1 == 0.0
 // x2 == -1.0
++------------------------------------------+
+
+  Note that the first part of the sum is maintained as the IEEE754 result. The 
<<<Sum>>>
+  is therefore not a full <<<double-double>>> implementation, which would 
maintain the sum
+  as the current total and a round-off term. This difference makes the 
<<<Sum>>> class faster
+  at the cost of some accuracy during addition of terms that cancel.
+  
+  If the terms to be subtracted are known then the summation can be split into 
the positive
+  and negative terms, summed in two parts and the result computed by a final 
subtraction of
+  the <<<Sum>>> of negative parts.
+
++------------------------------------------+
+double x1 = 1e100 + 1 - 2 - 1e100;
+Sum s1 = Sum.of(1e100, 1);
+Sum s2 = Sum.of(2, 1e100);
+double x2 = s1.subtract(s2).getAsDouble();
+// x1 == 0.0
+// x2 == -1.0
 +------------------------------------------+
 
   The class can be used to:
 
+  * Provide accurate summation of numbers with the same sign irrespective of 
input order;
+
   * Reduce cancellation effects that occur when large magnitude numbers
     with opposite signs are summed together with relatively small values;
 
-  * Provide accurate summation of numbers with the same sign irrespective of 
input order.
+  * Compute using two sums the terms of the same sign and the final result by 
combining
+    the sums (using add or subtract).
 
   []
 

Reply via email to