aherbert commented on code in PR #51:
URL: https://github.com/apache/commons-statistics/pull/51#discussion_r1289052638


##########
commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/FirstMoment.java:
##########
@@ -71,26 +65,34 @@ class FirstMoment implements DoubleStatistic, 
DoubleStatisticAccumulator<FirstMo
      */
     private double nDev;
 
+    /**
+     * Running sum of values seen so far.
+     * This is not used in the computation of mean. Used as a return value for 
first moment when
+     * it is non-finite.
+     */
+    private double nonFiniteValue;
+
     /**
      * Create a FirstMoment instance.
      */
     FirstMoment() {
         n = 0;

Review Comment:
   Setting these values to zero is void. There are initialised to zero by the 
JVM anyway. So the constructor can be empty.



##########
commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/FirstMoment.java:
##########
@@ -99,70 +101,60 @@ class FirstMoment implements DoubleStatistic, 
DoubleStatisticAccumulator<FirstMo
      */
     @Override
     public void accept(double value) {
-        if (n == 0) {
-            m1 = 0.0;
-        }
         n++;
-        if (value == Double.POSITIVE_INFINITY) {
-            posInfCount++;
-        } else if (value == Double.NEGATIVE_INFINITY) {
-            negInfCount++;
-        } else {
-            //nDev is computed in this manner to prevent overflow.
-            nDev = (value / n) - (m1 / n);
-            dev = nDev * n;
-            m1 += nDev;
-        }
+        nonFiniteValue += value;
+        dev = (value * 0.5 - m1 * 0.5) * 2;

Review Comment:
   You should maintain the comment about preventing overflow. Otherwise it is 
not clear why we scaled down and then back up.



##########
commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/FirstMoment.java:
##########
@@ -99,70 +101,60 @@ class FirstMoment implements DoubleStatistic, 
DoubleStatisticAccumulator<FirstMo
      */
     @Override
     public void accept(double value) {
-        if (n == 0) {
-            m1 = 0.0;
-        }
         n++;
-        if (value == Double.POSITIVE_INFINITY) {
-            posInfCount++;
-        } else if (value == Double.NEGATIVE_INFINITY) {
-            negInfCount++;
-        } else {
-            //nDev is computed in this manner to prevent overflow.
-            nDev = (value / n) - (m1 / n);
-            dev = nDev * n;
-            m1 += nDev;
-        }
+        nonFiniteValue += value;
+        dev = (value * 0.5 - m1 * 0.5) * 2;
+        nDev = dev / n;
+        m1 += nDev;
     }
 
     /**
      * Gets the first moment of all input values.
      *
      * <p>When no values have been added, the result is <code>NaN</code>.
      *
-     * @return {@code First moment} of all values seen so far.
+     * @return {@code First moment} of all values seen so far, if it is 
finite, else,
+     * the non-finite sum of all input values which is the same as their 
{@code first moment}.
      */
     @Override
     public double getAsDouble() {
-        if (posInfCount == 0 && negInfCount == 0) {
-            return m1;
-        } else if (posInfCount == 0) {
-            return Double.NEGATIVE_INFINITY;
-        } else if (negInfCount == 0) {
-            return Double.POSITIVE_INFINITY;
-        } else {
-            return Double.NaN;
+        if (Double.isFinite(m1)) {
+            return n > 0 ? m1 : Double.NaN;
         }
+        return nonFiniteValue;

Review Comment:
   Add a comment here that non-finite values must have been encountered



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {

Review Comment:
   This test is redundant with `testMean`. I would remove it and move the 2 ULP 
tolerance to that method.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),
+            Arguments.of(new double[] {1, -7, 6}),
+            Arguments.of(new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}),
+            Arguments.of(new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0, +0.0}),
+            Arguments.of(new double[] {0.0, -0.0}),
+            Arguments.of(new double[] {0.0, +0.0}),
+            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004}),
+            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE, Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),

Review Comment:
   This is a duplicate from line 94.
   
   We should have a case that is computable but overflows a sum. You have {MAX, 
MAX}. Try {MAX, MAX / 2} as this is different from a mean of all equal values.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java:
##########
@@ -30,6 +30,23 @@ final class TestHelper {
     /** Class contains only static methods. */
     private TestHelper() {}
 
+    /**
+     * Helper function to concatenate two arrays.
+     *
+     * @param array1 First array to be concatenated.
+     * @param array2 Second array to be concatenated.
+     * @return A new array containing elements from both input arrays in the 
order they appear.
+     */
+    static double[] concatenate(double[] array1, double[] array2) {

Review Comment:
   Originally I thought this could be removed by changing your 
`computeExpected` to accept multiple arrays. But then it is simpler to compute 
the stat from a single array. This pattern is better going forward when a test 
may compute other more complicated stats such as the higher order moments.
   
   However it may be helpful for this to be generic, i.e. allow more than 2 
arrays. You could then add better test cases for combine with more than 2 
arrays:
   ```java
           return 
Arrays.stream(arrays).flatMapToDouble(Arrays::stream).toArray();
   ```
   It's not as efficient when you only have 2 arrays but it is more flexible.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
-            testMean(TestHelper.shuffle(rng, values), expected);
-            testParallelStream(TestHelper.shuffle(rng, values), expected);
+            TestHelper.shuffle(rng, array1);
+            TestHelper.shuffle(rng, array2);
+            testCombine(array1, array2);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = {"testCombine"})
-    void testCombine(double[][] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testArrayOfArrays(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        final double[][] values = {array1, array2};
+        double actual = Arrays.stream(values)
+                .map(Mean::of)
+                .reduce(Mean::combine)
+                .map(Mean::getAsDouble)
+                .orElseThrow(RuntimeException::new);
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+    }
+
+    static Stream<Arguments> testCombine() {
+        return Stream.of(
+            Arguments.of(new double[] {}, new double[] {1}),
+            Arguments.of(new double[] {1}, new double[] {}),
+            Arguments.of(new double[] {}, new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {0}, new double[] {0, 0.0}),
+            Arguments.of(new double[] {4, 8, -6, 3, 18}, new double[] {1, -7, 
6}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 
5.73}),
+            Arguments.of(new double[] {6.0, -1.32, -5.78, 8.967, 13.32, -9.67, 
0.14, 7.321, 11.456, -3.111}, new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}, new double[] {-42, 10, -88, 5, 
-17}),
+            Arguments.of(new double[] {-20, 34.983, -12.745, 28.12, -8.34, 42, 
-4, 16}, new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, new double[] {12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {-0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {10E-50, 5E-100}, new double[] {25E-200, 
35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1}, new double[] 
{1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 3.1415E153}, new 
double[] {}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1}, new double[] {-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] {1, 
1E300})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineNonFinite(double[][] values, double expected) {
         Mean mean1 = Mean.create();
         Mean mean2 = Mean.create();
         Arrays.stream(values[0]).forEach(mean1);
         Arrays.stream(values[1]).forEach(mean2);
         double mean2BeforeCombine = mean2.getAsDouble();
         mean1.combine(mean2);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 10, () -> 
"combine");
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> 
"combine");
         Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
-    static Stream<Arguments> testCombine() {
-        return Stream.of(
-            Arguments.of(new double[][] {{}, {}}, Double.NaN),
-            Arguments.of(new double[][] {{}, {1}}, 1),
-            Arguments.of(new double[][] {{1}, {}}, 1),
-            Arguments.of(new double[][] {{}, {1, 7, -15, 3}}, -1),
-            Arguments.of(new double[][] {{0}, {0, 0.0}}, 0.0),
-            Arguments.of(new double[][] {{4, 8, -6, 3, 18}, {1, -7, 6}}, 
3.375),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}}, 9),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73}}, 8.25),
-            Arguments.of(new double[][] {{6.0, -1.32, -5.78, 8.967, 13.32, 
-9.67, 0.14, 7.321, 11.456, -3.111}, {2, 2, 2, 2}}, 2.52307142857142857),
-            Arguments.of(new double[][] {{2.3}, {-42, 10, -88, 5, -17}}, 
-21.61666666666666666),
-            Arguments.of(new double[][] {{-20, 34.983, -12.745, 28.12, -8.34, 
42, -4, 16}, {3.14, 2.718, 1.414}}, 7.571818181818182),
-            Arguments.of(new double[][] {{12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, {12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}}, 12.404545454545454),
-            Arguments.of(new double[][] {{-0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {-0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{10E-50, 5E-100}, {25E-200, 
35.345E-50}},
-                1.133625E-49),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, 
{Double.MAX_VALUE}},
-                Double.MAX_VALUE),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{-Double.MAX_VALUE}},
-                -Double.MAX_VALUE),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
-            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, {1, 1E300}}, 
5.992310482874387E307),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE, 1}, {1}}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[][] {{Double.MAX_VALUE, 3.1415E153}, {}}, 
Double.MAX_VALUE / 2)
-        );
-    }
-
     @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testCombineRandomOrder(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineRandomOrderNonFinite(double[][] values, double expected) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
             TestHelper.shuffle(rng, values[0]);
             TestHelper.shuffle(rng, values[1]);
-            testCombine(values, expected);
+            testCombineNonFinite(values, expected);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testTwoPass(double[] values, double expected) {
-        Mean mean1 = Mean.of(values);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
-    }
-
-    @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testArrayOfArrays(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testArrayOfArraysNonFinite(double[][] values, double expected) {
         double actual = Arrays.stream(values)
                 .map(Mean::of)
                 .reduce(Mean::combine)
                 .map(Mean::getAsDouble)
                 .orElseThrow(RuntimeException::new);
-        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean non-finite");

Review Comment:
   Assertions.assertEquals



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
-            testMean(TestHelper.shuffle(rng, values), expected);
-            testParallelStream(TestHelper.shuffle(rng, values), expected);
+            TestHelper.shuffle(rng, array1);
+            TestHelper.shuffle(rng, array2);
+            testCombine(array1, array2);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = {"testCombine"})
-    void testCombine(double[][] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testArrayOfArrays(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        final double[][] values = {array1, array2};
+        double actual = Arrays.stream(values)
+                .map(Mean::of)
+                .reduce(Mean::combine)
+                .map(Mean::getAsDouble)
+                .orElseThrow(RuntimeException::new);
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+    }
+
+    static Stream<Arguments> testCombine() {
+        return Stream.of(
+            Arguments.of(new double[] {}, new double[] {1}),
+            Arguments.of(new double[] {1}, new double[] {}),
+            Arguments.of(new double[] {}, new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {0}, new double[] {0, 0.0}),
+            Arguments.of(new double[] {4, 8, -6, 3, 18}, new double[] {1, -7, 
6}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 
5.73}),
+            Arguments.of(new double[] {6.0, -1.32, -5.78, 8.967, 13.32, -9.67, 
0.14, 7.321, 11.456, -3.111}, new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}, new double[] {-42, 10, -88, 5, 
-17}),
+            Arguments.of(new double[] {-20, 34.983, -12.745, 28.12, -8.34, 42, 
-4, 16}, new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, new double[] {12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {-0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {10E-50, 5E-100}, new double[] {25E-200, 
35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1}, new double[] 
{1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 3.1415E153}, new 
double[] {}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1}, new double[] {-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] {1, 
1E300})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineNonFinite(double[][] values, double expected) {
         Mean mean1 = Mean.create();
         Mean mean2 = Mean.create();
         Arrays.stream(values[0]).forEach(mean1);
         Arrays.stream(values[1]).forEach(mean2);
         double mean2BeforeCombine = mean2.getAsDouble();
         mean1.combine(mean2);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 10, () -> 
"combine");
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> 
"combine");

Review Comment:
   Assertions.assertEquals



##########
commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/FirstMoment.java:
##########
@@ -99,70 +101,60 @@ class FirstMoment implements DoubleStatistic, 
DoubleStatisticAccumulator<FirstMo
      */
     @Override
     public void accept(double value) {
-        if (n == 0) {
-            m1 = 0.0;
-        }
         n++;
-        if (value == Double.POSITIVE_INFINITY) {
-            posInfCount++;
-        } else if (value == Double.NEGATIVE_INFINITY) {
-            negInfCount++;
-        } else {
-            //nDev is computed in this manner to prevent overflow.
-            nDev = (value / n) - (m1 / n);
-            dev = nDev * n;
-            m1 += nDev;
-        }
+        nonFiniteValue += value;
+        dev = (value * 0.5 - m1 * 0.5) * 2;
+        nDev = dev / n;
+        m1 += nDev;
     }
 
     /**
      * Gets the first moment of all input values.
      *
      * <p>When no values have been added, the result is <code>NaN</code>.
      *
-     * @return {@code First moment} of all values seen so far.
+     * @return {@code First moment} of all values seen so far, if it is 
finite, else,
+     * the non-finite sum of all input values which is the same as their 
{@code first moment}.
      */
     @Override
     public double getAsDouble() {
-        if (posInfCount == 0 && negInfCount == 0) {
-            return m1;
-        } else if (posInfCount == 0) {
-            return Double.NEGATIVE_INFINITY;
-        } else if (negInfCount == 0) {
-            return Double.POSITIVE_INFINITY;
-        } else {
-            return Double.NaN;
+        if (Double.isFinite(m1)) {
+            return n > 0 ? m1 : Double.NaN;

Review Comment:
   It makes more sense to be conditioned on `n == 0` here as this is the reason 
for the NaN (a divide by zero)



##########
commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/FirstMoment.java:
##########
@@ -99,70 +101,60 @@ class FirstMoment implements DoubleStatistic, 
DoubleStatisticAccumulator<FirstMo
      */
     @Override
     public void accept(double value) {
-        if (n == 0) {
-            m1 = 0.0;
-        }
         n++;
-        if (value == Double.POSITIVE_INFINITY) {
-            posInfCount++;
-        } else if (value == Double.NEGATIVE_INFINITY) {
-            negInfCount++;
-        } else {
-            //nDev is computed in this manner to prevent overflow.
-            nDev = (value / n) - (m1 / n);
-            dev = nDev * n;
-            m1 += nDev;
-        }
+        nonFiniteValue += value;
+        dev = (value * 0.5 - m1 * 0.5) * 2;
+        nDev = dev / n;
+        m1 += nDev;
     }
 
     /**
      * Gets the first moment of all input values.
      *
      * <p>When no values have been added, the result is <code>NaN</code>.
      *
-     * @return {@code First moment} of all values seen so far.
+     * @return {@code First moment} of all values seen so far, if it is 
finite, else,
+     * the non-finite sum of all input values which is the same as their 
{@code first moment}.

Review Comment:
   No need to state we are returning the non-finite sum. This can state it 
returns infinity if infinities of the same sign have been encountered else NaN.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),
+            Arguments.of(new double[] {1, -7, 6}),
+            Arguments.of(new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}),
+            Arguments.of(new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0, +0.0}),
+            Arguments.of(new double[] {0.0, -0.0}),
+            Arguments.of(new double[] {0.0, +0.0}),
+            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004}),
+            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE, Double.MAX_VALUE}),

Review Comment:
   comment: // Overflow of the sum



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");

Review Comment:
   Can the two-pass algorithm use a lower ULP



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),
+            Arguments.of(new double[] {1, -7, 6}),
+            Arguments.of(new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}),
+            Arguments.of(new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0, +0.0}),
+            Arguments.of(new double[] {0.0, -0.0}),
+            Arguments.of(new double[] {0.0, +0.0}),
+            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004}),
+            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE, Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1, -Double.MAX_VALUE})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testMeanNonFinite(double[] values, double expected) {
+        Mean mean = Mean.create();
+        for (double value : values) {
+            mean.accept(value);
+        }
+        TestHelper.assertEquals(expected, mean.getAsDouble(), 2, () -> "mean 
non-finite");

Review Comment:
   Since these are non-finite then we can use `Assertions.assertEquals` without 
the ulp tolerance. These should be exact. Note this is allowed:
   ```java
   TestHelper.assertEquals(Double.POSITIVE_INFINITY, Double.MAX_VALUE, 1, () -> 
"oops");
   ```
   We should update the TestHelper so it can detect non-finite inputs and uses 
binary equality. We really only want to use a ULP tolerance between finite 
numbers. This is a limitation of the Precision class which uses a count of the 
representable doubles between two values. This does not lend itself well to 
testing values that are close to infinity, e.g. MAX_VALUE, like we do in this 
class.
   
   The current test suite for other statistics works if using this:
   ```java
   static void assertEquals(double expected, double actual, int ulps, 
Supplier<String> msg) {
       // Require strict equivalence of non-finite values
       if (Double.isFinite(expected) && Double.isFinite(actual)) {
           Assertions.assertTrue(Precision.equals(expected, actual, ulps),
               () -> expected + " != " + actual + " within " + ulps + " ulp(s): 
" + msg.get());
       } else {
           Assertions.assertEquals(expected, actual, msg);
       }
   }
   ```
   But some of the MeanTest cases fail because the `computeExpected` uses a 
DECIMAL64 math context for the divide which rounds the value above 
Double.MAX_VALUE. Then the conversion to double creates infinity. If using 
DECIMAL128 then the MeanTest works.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),

Review Comment:
   An edge case you are missing is a single zero {0.0}



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
-            testMean(TestHelper.shuffle(rng, values), expected);
-            testParallelStream(TestHelper.shuffle(rng, values), expected);
+            TestHelper.shuffle(rng, array1);
+            TestHelper.shuffle(rng, array2);
+            testCombine(array1, array2);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = {"testCombine"})
-    void testCombine(double[][] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testArrayOfArrays(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        final double[][] values = {array1, array2};
+        double actual = Arrays.stream(values)
+                .map(Mean::of)
+                .reduce(Mean::combine)
+                .map(Mean::getAsDouble)
+                .orElseThrow(RuntimeException::new);
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+    }
+
+    static Stream<Arguments> testCombine() {
+        return Stream.of(
+            Arguments.of(new double[] {}, new double[] {1}),
+            Arguments.of(new double[] {1}, new double[] {}),
+            Arguments.of(new double[] {}, new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {0}, new double[] {0, 0.0}),
+            Arguments.of(new double[] {4, 8, -6, 3, 18}, new double[] {1, -7, 
6}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 
5.73}),
+            Arguments.of(new double[] {6.0, -1.32, -5.78, 8.967, 13.32, -9.67, 
0.14, 7.321, 11.456, -3.111}, new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}, new double[] {-42, 10, -88, 5, 
-17}),
+            Arguments.of(new double[] {-20, 34.983, -12.745, 28.12, -8.34, 42, 
-4, 16}, new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, new double[] {12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {-0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {10E-50, 5E-100}, new double[] {25E-200, 
35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1}, new double[] 
{1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 3.1415E153}, new 
double[] {}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1}, new double[] {-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] {1, 
1E300})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineNonFinite(double[][] values, double expected) {
         Mean mean1 = Mean.create();
         Mean mean2 = Mean.create();
         Arrays.stream(values[0]).forEach(mean1);
         Arrays.stream(values[1]).forEach(mean2);
         double mean2BeforeCombine = mean2.getAsDouble();
         mean1.combine(mean2);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 10, () -> 
"combine");
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> 
"combine");
         Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
-    static Stream<Arguments> testCombine() {
-        return Stream.of(
-            Arguments.of(new double[][] {{}, {}}, Double.NaN),
-            Arguments.of(new double[][] {{}, {1}}, 1),
-            Arguments.of(new double[][] {{1}, {}}, 1),
-            Arguments.of(new double[][] {{}, {1, 7, -15, 3}}, -1),
-            Arguments.of(new double[][] {{0}, {0, 0.0}}, 0.0),
-            Arguments.of(new double[][] {{4, 8, -6, 3, 18}, {1, -7, 6}}, 
3.375),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}}, 9),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73}}, 8.25),
-            Arguments.of(new double[][] {{6.0, -1.32, -5.78, 8.967, 13.32, 
-9.67, 0.14, 7.321, 11.456, -3.111}, {2, 2, 2, 2}}, 2.52307142857142857),
-            Arguments.of(new double[][] {{2.3}, {-42, 10, -88, 5, -17}}, 
-21.61666666666666666),
-            Arguments.of(new double[][] {{-20, 34.983, -12.745, 28.12, -8.34, 
42, -4, 16}, {3.14, 2.718, 1.414}}, 7.571818181818182),
-            Arguments.of(new double[][] {{12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, {12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}}, 12.404545454545454),
-            Arguments.of(new double[][] {{-0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {-0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{10E-50, 5E-100}, {25E-200, 
35.345E-50}},
-                1.133625E-49),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, 
{Double.MAX_VALUE}},
-                Double.MAX_VALUE),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{-Double.MAX_VALUE}},
-                -Double.MAX_VALUE),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
-            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, {1, 1E300}}, 
5.992310482874387E307),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE, 1}, {1}}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[][] {{Double.MAX_VALUE, 3.1415E153}, {}}, 
Double.MAX_VALUE / 2)
-        );
-    }
-
     @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testCombineRandomOrder(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineRandomOrderNonFinite(double[][] values, double expected) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
             TestHelper.shuffle(rng, values[0]);
             TestHelper.shuffle(rng, values[1]);
-            testCombine(values, expected);
+            testCombineNonFinite(values, expected);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testTwoPass(double[] values, double expected) {
-        Mean mean1 = Mean.of(values);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
-    }
-
-    @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testArrayOfArrays(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testArrayOfArraysNonFinite(double[][] values, double expected) {
         double actual = Arrays.stream(values)
                 .map(Mean::of)
                 .reduce(Mean::combine)
                 .map(Mean::getAsDouble)
                 .orElseThrow(RuntimeException::new);
-        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean non-finite");
+    }
+
+    static Stream<Arguments> testCombineNonFinite() {
+        return Stream.of(
+            Arguments.of(new double[][] {{}, {}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}}, Double.NEGATIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}}, Double.NEGATIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
+            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}}, 
Double.NEGATIVE_INFINITY)
+        );
+    }
+
+    // Helper function to compute the expected value of Mean using BigDecimal.
+    static double computeExpected(double[] values) {
+        BigDecimal bd = BigDecimal.valueOf(0.0);
+        for (double value : values) {
+            bd = bd.add(new BigDecimal(value));
+        }
+        return bd.divide(BigDecimal.valueOf(values.length), 
MathContext.DECIMAL64).doubleValue();

Review Comment:
   Use DECIMAL128 here. The final rounding is done in doubleValue().



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
-            testMean(TestHelper.shuffle(rng, values), expected);
-            testParallelStream(TestHelper.shuffle(rng, values), expected);
+            TestHelper.shuffle(rng, array1);
+            TestHelper.shuffle(rng, array2);
+            testCombine(array1, array2);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = {"testCombine"})
-    void testCombine(double[][] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testArrayOfArrays(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        final double[][] values = {array1, array2};
+        double actual = Arrays.stream(values)
+                .map(Mean::of)
+                .reduce(Mean::combine)
+                .map(Mean::getAsDouble)
+                .orElseThrow(RuntimeException::new);
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+    }
+
+    static Stream<Arguments> testCombine() {
+        return Stream.of(
+            Arguments.of(new double[] {}, new double[] {1}),
+            Arguments.of(new double[] {1}, new double[] {}),
+            Arguments.of(new double[] {}, new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {0}, new double[] {0, 0.0}),
+            Arguments.of(new double[] {4, 8, -6, 3, 18}, new double[] {1, -7, 
6}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 
5.73}),
+            Arguments.of(new double[] {6.0, -1.32, -5.78, 8.967, 13.32, -9.67, 
0.14, 7.321, 11.456, -3.111}, new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}, new double[] {-42, 10, -88, 5, 
-17}),
+            Arguments.of(new double[] {-20, 34.983, -12.745, 28.12, -8.34, 42, 
-4, 16}, new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, new double[] {12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {-0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {10E-50, 5E-100}, new double[] {25E-200, 
35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1}, new double[] 
{1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 3.1415E153}, new 
double[] {}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1}, new double[] {-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] {1, 
1E300})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineNonFinite(double[][] values, double expected) {
         Mean mean1 = Mean.create();
         Mean mean2 = Mean.create();
         Arrays.stream(values[0]).forEach(mean1);
         Arrays.stream(values[1]).forEach(mean2);
         double mean2BeforeCombine = mean2.getAsDouble();
         mean1.combine(mean2);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 10, () -> 
"combine");
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> 
"combine");
         Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
-    static Stream<Arguments> testCombine() {
-        return Stream.of(
-            Arguments.of(new double[][] {{}, {}}, Double.NaN),
-            Arguments.of(new double[][] {{}, {1}}, 1),
-            Arguments.of(new double[][] {{1}, {}}, 1),
-            Arguments.of(new double[][] {{}, {1, 7, -15, 3}}, -1),
-            Arguments.of(new double[][] {{0}, {0, 0.0}}, 0.0),
-            Arguments.of(new double[][] {{4, 8, -6, 3, 18}, {1, -7, 6}}, 
3.375),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}}, 9),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73}}, 8.25),
-            Arguments.of(new double[][] {{6.0, -1.32, -5.78, 8.967, 13.32, 
-9.67, 0.14, 7.321, 11.456, -3.111}, {2, 2, 2, 2}}, 2.52307142857142857),
-            Arguments.of(new double[][] {{2.3}, {-42, 10, -88, 5, -17}}, 
-21.61666666666666666),
-            Arguments.of(new double[][] {{-20, 34.983, -12.745, 28.12, -8.34, 
42, -4, 16}, {3.14, 2.718, 1.414}}, 7.571818181818182),
-            Arguments.of(new double[][] {{12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, {12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}}, 12.404545454545454),
-            Arguments.of(new double[][] {{-0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {-0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{10E-50, 5E-100}, {25E-200, 
35.345E-50}},
-                1.133625E-49),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, 
{Double.MAX_VALUE}},
-                Double.MAX_VALUE),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{-Double.MAX_VALUE}},
-                -Double.MAX_VALUE),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
-            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, {1, 1E300}}, 
5.992310482874387E307),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE, 1}, {1}}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[][] {{Double.MAX_VALUE, 3.1415E153}, {}}, 
Double.MAX_VALUE / 2)
-        );
-    }
-
     @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testCombineRandomOrder(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineRandomOrderNonFinite(double[][] values, double expected) {

Review Comment:
   Again shuffle both within and across the arrays



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),
+            Arguments.of(new double[] {1, -7, 6}),
+            Arguments.of(new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}),
+            Arguments.of(new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0, +0.0}),
+            Arguments.of(new double[] {0.0, -0.0}),
+            Arguments.of(new double[] {0.0, +0.0}),
+            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004}),
+            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE, Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1, -Double.MAX_VALUE})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testMeanNonFinite(double[] values, double expected) {
+        Mean mean = Mean.create();
+        for (double value : values) {
+            mean.accept(value);
+        }
+        TestHelper.assertEquals(expected, mean.getAsDouble(), 2, () -> "mean 
non-finite");
+        TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 2, () 
-> "of (values) non-finite");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testParallelStreamNonFinite(double[] values, double expected) {
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 2, () -> "parallel stream 
non-finite");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testMeanRandomOrderNonFinite(double[] values, double expected) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMeanNonFinite(TestHelper.shuffle(rng, values), expected);
+            testParallelStreamNonFinite(TestHelper.shuffle(rng, values), 
expected);
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testTwoPassNonFinite(double[] values, double expected) {

Review Comment:
   This is a duplicate of `testMeanNonFinite`. I would remove this.



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
-            testMean(TestHelper.shuffle(rng, values), expected);
-            testParallelStream(TestHelper.shuffle(rng, values), expected);
+            TestHelper.shuffle(rng, array1);
+            TestHelper.shuffle(rng, array2);
+            testCombine(array1, array2);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = {"testCombine"})
-    void testCombine(double[][] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testArrayOfArrays(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        final double[][] values = {array1, array2};
+        double actual = Arrays.stream(values)
+                .map(Mean::of)
+                .reduce(Mean::combine)
+                .map(Mean::getAsDouble)
+                .orElseThrow(RuntimeException::new);
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+    }
+
+    static Stream<Arguments> testCombine() {
+        return Stream.of(
+            Arguments.of(new double[] {}, new double[] {1}),
+            Arguments.of(new double[] {1}, new double[] {}),
+            Arguments.of(new double[] {}, new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {0}, new double[] {0, 0.0}),
+            Arguments.of(new double[] {4, 8, -6, 3, 18}, new double[] {1, -7, 
6}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 
5.73}),
+            Arguments.of(new double[] {6.0, -1.32, -5.78, 8.967, 13.32, -9.67, 
0.14, 7.321, 11.456, -3.111}, new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}, new double[] {-42, 10, -88, 5, 
-17}),
+            Arguments.of(new double[] {-20, 34.983, -12.745, 28.12, -8.34, 42, 
-4, 16}, new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, new double[] {12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {-0.0}),
+            Arguments.of(new double[] {0.0}, new double[] {+0.0}),
+            Arguments.of(new double[] {10E-50, 5E-100}, new double[] {25E-200, 
35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1}, new double[] 
{1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 3.1415E153}, new 
double[] {}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1}, new double[] {-Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1}, new double[] 
{-Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE}, new double[] {1, 
1E300})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineNonFinite(double[][] values, double expected) {
         Mean mean1 = Mean.create();
         Mean mean2 = Mean.create();
         Arrays.stream(values[0]).forEach(mean1);
         Arrays.stream(values[1]).forEach(mean2);
         double mean2BeforeCombine = mean2.getAsDouble();
         mean1.combine(mean2);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 10, () -> 
"combine");
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> 
"combine");
         Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
-    static Stream<Arguments> testCombine() {
-        return Stream.of(
-            Arguments.of(new double[][] {{}, {}}, Double.NaN),
-            Arguments.of(new double[][] {{}, {1}}, 1),
-            Arguments.of(new double[][] {{1}, {}}, 1),
-            Arguments.of(new double[][] {{}, {1, 7, -15, 3}}, -1),
-            Arguments.of(new double[][] {{0}, {0, 0.0}}, 0.0),
-            Arguments.of(new double[][] {{4, 8, -6, 3, 18}, {1, -7, 6}}, 
3.375),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}}, 9),
-            Arguments.of(new double[][] {{10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 
5}, {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73}}, 8.25),
-            Arguments.of(new double[][] {{6.0, -1.32, -5.78, 8.967, 13.32, 
-9.67, 0.14, 7.321, 11.456, -3.111}, {2, 2, 2, 2}}, 2.52307142857142857),
-            Arguments.of(new double[][] {{2.3}, {-42, 10, -88, 5, -17}}, 
-21.61666666666666666),
-            Arguments.of(new double[][] {{-20, 34.983, -12.745, 28.12, -8.34, 
42, -4, 16}, {3.14, 2.718, 1.414}}, 7.571818181818182),
-            Arguments.of(new double[][] {{12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9}, {12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}}, 12.404545454545454),
-            Arguments.of(new double[][] {{-0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {-0.0}}, 0.0),
-            Arguments.of(new double[][] {{0.0}, {+0.0}}, 0.0),
-            Arguments.of(new double[][] {{10E-50, 5E-100}, {25E-200, 
35.345E-50}},
-                1.133625E-49),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, 
{Double.MAX_VALUE}},
-                Double.MAX_VALUE),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}},
-                Double.POSITIVE_INFINITY),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{-Double.MAX_VALUE}},
-                -Double.MAX_VALUE),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
-            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}},
-                Double.NaN),
-            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
-            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}},
-                Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[][] {{Double.MAX_VALUE}, {1, 1E300}}, 
5.992310482874387E307),
-            Arguments.of(new double[][] {{-Double.MAX_VALUE, 1}, {1}}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[][] {{Double.MAX_VALUE, 3.1415E153}, {}}, 
Double.MAX_VALUE / 2)
-        );
-    }
-
     @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testCombineRandomOrder(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testCombineRandomOrderNonFinite(double[][] values, double expected) {
         UniformRandomProvider rng = TestHelper.createRNG();
         for (int i = 1; i <= 10; i++) {
             TestHelper.shuffle(rng, values[0]);
             TestHelper.shuffle(rng, values[1]);
-            testCombine(values, expected);
+            testCombineNonFinite(values, expected);
         }
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testTwoPass(double[] values, double expected) {
-        Mean mean1 = Mean.of(values);
-        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
-    }
-
-    @ParameterizedTest
-    @MethodSource(value = "testCombine")
-    void testArrayOfArrays(double[][] values, double expected) {
+    @MethodSource(value = "testCombineNonFinite")
+    void testArrayOfArraysNonFinite(double[][] values, double expected) {
         double actual = Arrays.stream(values)
                 .map(Mean::of)
                 .reduce(Mean::combine)
                 .map(Mean::getAsDouble)
                 .orElseThrow(RuntimeException::new);
-        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean");
+        TestHelper.assertEquals(expected, actual, 2, () -> "array of arrays 
combined mean non-finite");
+    }
+
+    static Stream<Arguments> testCombineNonFinite() {
+        return Stream.of(
+            Arguments.of(new double[][] {{}, {}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.POSITIVE_INFINITY}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{Double.NEGATIVE_INFINITY}}, Double.NEGATIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.POSITIVE_INFINITY}, 
{Double.MAX_VALUE}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{-Double.MAX_VALUE}, 
{Double.POSITIVE_INFINITY}}, Double.POSITIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY}, 
{-Double.MIN_VALUE}}, Double.NEGATIVE_INFINITY),
+            Arguments.of(new double[][] {{Double.NaN, 34.56, 89.74}, 
{Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{34.56}, {Double.NaN, 89.74}}, 
Double.NaN),
+            Arguments.of(new double[][] {{34.56, 89.74}, {Double.NaN, 
Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NaN, 3.14, Double.NaN, 
Double.NaN}, {}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NaN, Double.NaN, Double.NaN}, 
{Double.NaN, Double.NaN, Double.NaN}}, Double.NaN),
+            Arguments.of(new double[][] {{Double.NEGATIVE_INFINITY, 
-Double.MAX_VALUE, -Double.MIN_VALUE}, {Double.MAX_VALUE, Double.MIN_VALUE}}, 
Double.NEGATIVE_INFINITY)
+        );
+    }
+
+    // Helper function to compute the expected value of Mean using BigDecimal.
+    static double computeExpected(double[] values) {
+        BigDecimal bd = BigDecimal.valueOf(0.0);

Review Comment:
   BigDecimal.ZERO



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -106,124 +180,140 @@ static Stream<Arguments> testMean() {
             Arguments.of(new double[] {Double.NaN, Double.NaN, Double.NaN}, 
Double.NaN),
             Arguments.of(new double[] {Double.NEGATIVE_INFINITY, 
Double.MAX_VALUE},
                 Double.NEGATIVE_INFINITY),
-            Arguments.of(new double[] {Double.MAX_VALUE, 1}, Double.MAX_VALUE 
/ 2),
-            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}, 
-Double.MAX_VALUE / 3),
-            Arguments.of(new double[] {Double.MAX_VALUE, -1}, Double.MAX_VALUE 
/ 2),
             Arguments.of(new double[] {Double.POSITIVE_INFINITY, 
Double.POSITIVE_INFINITY,
-                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY)
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY}, 
Double.POSITIVE_INFINITY),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 
Double.POSITIVE_INFINITY},
+                Double.POSITIVE_INFINITY)
         );
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testParallelStream(double[] values, double expected) {
-        double ans = Arrays.stream(values)
-                .parallel()
-                .collect(Mean::create, Mean::accept, Mean::combine)
-                .getAsDouble();
-
-        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    @MethodSource(value = "testCombine")
+    void testCombine(double[] array1, double[] array2) {
+        final double[] combinedArray = TestHelper.concatenate(array1, array2);
+        final double expected = computeExpected(combinedArray);
+        Mean mean1 = Mean.create();
+        Mean mean2 = Mean.create();
+        Arrays.stream(array1).forEach(mean1);
+        Arrays.stream(array2).forEach(mean2);
+        final double mean2BeforeCombine = mean2.getAsDouble();
+        mean1.combine(mean2);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 5, () -> 
"combine");
+        Assertions.assertEquals(mean2BeforeCombine, mean2.getAsDouble());
     }
 
     @ParameterizedTest
-    @MethodSource(value = "testMean")
-    void testMeanRandomOrder(double[] values, double expected) {
+    @MethodSource(value = "testCombine")
+    void testCombineRandomOrder(double[] array1, double[] array2) {

Review Comment:
   Perhaps we should shuffle across the two arrays, either instead of, or as 
well as what you have here. This is the shuffle across the arrays:
   ```java
           double[] data = TestHelper.concatenate(array1, array2);
           int n = array1.length;
           for (int i = 1; i <= 10; i++) {
               TestHelper.shuffle(rng, data);
               System.arraycopy(data, 0, array1, 0, n);
               System.arraycopy(data, n, array2, 0, array2.length);
               testCombine(array1, array2);
           }
   ```



##########
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/MeanTest.java:
##########
@@ -57,31 +60,108 @@ void testMean(double[] values, double expected) {
         TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 5, () 
-> "of (values)");
     }
 
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testParallelStream(double[] values) {
+        double expected = computeExpected(values);
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 5, () -> "parallel stream");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testMeanRandomOrder(double[] values) {
+        UniformRandomProvider rng = TestHelper.createRNG();
+        for (int i = 1; i <= 10; i++) {
+            testMean(TestHelper.shuffle(rng, values));
+            testParallelStream(TestHelper.shuffle(rng, values));
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMean")
+    void testTwoPass(double[] values) {
+        double expected = computeExpected(values);
+        Mean mean1 = Mean.of(values);
+        TestHelper.assertEquals(expected, mean1.getAsDouble(), 2, () -> "mean 
of");
+    }
+
     static Stream<Arguments> testMean() {
         return Stream.of(
-            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}, 
9),
-            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}, 7.500909090909092),
-            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74}, 7.500909090909092),
-            Arguments.of(new double[] {7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 
6.08, 5.39, 8.15, 6.42, 5.73}, 7.5),
-            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}, 9),
-            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}, 7.500909090909092),
-            Arguments.of(new double[]{}, Double.NaN),
-            Arguments.of(new double[] {0, 0, 0.0}, 0.0),
-            Arguments.of(new double[] {1, -7, 6}, 0),
-            Arguments.of(new double[] {1, 7, -15, 3}, -1),
-            Arguments.of(new double[] {2, 2, 2, 2}, 2.0),
-            Arguments.of(new double[] {2.3}, 2.3),
-            Arguments.of(new double[] {3.14, 2.718, 1.414}, 2.424),
-            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0,
-                8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 
11.0, 10.0, 8.8,
-                9.0, 12.3}, 12.404545454545454),
-            Arguments.of(new double[] {-0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.0, -0.0}, 0.0),
-            Arguments.of(new double[] {0.0, +0.0}, 0.0),
-            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004},
-                2000.0222468),
-            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50},
-                1.133625E-49),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5}),
+            Arguments.of(new double[] {8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 
7.24, 4.26, 10.84, 4.82, 5.68}),
+            Arguments.of(new double[] {9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 
6.13, 3.10, 9.13, 7.26, 4.74, 7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 
8.15, 6.42, 5.73}),
+            Arguments.of(new double[] {8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8}),
+            Arguments.of(new double[] {6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 
5.25, 12.50, 5.56, 7.91, 6.89}),
+            Arguments.of(new double[] {0, 0, 0.0}),
+            Arguments.of(new double[] {1, -7, 6}),
+            Arguments.of(new double[] {1, 7, -15, 3}),
+            Arguments.of(new double[] {2, 2, 2, 2}),
+            Arguments.of(new double[] {2.3}),
+            Arguments.of(new double[] {3.14, 2.718, 1.414}),
+            Arguments.of(new double[] {12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 
21.0, 8.2, 10.3, 11.3, 14.1, 9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0, 
8.8, 9.0, 12.3}),
+            Arguments.of(new double[] {-0.0, +0.0}),
+            Arguments.of(new double[] {0.0, -0.0}),
+            Arguments.of(new double[] {0.0, +0.0}),
+            Arguments.of(new double[] {0.001, 0.0002, 0.00003, 10000.11, 
0.000004}),
+            Arguments.of(new double[] {10E-50, 5E-100, 25E-200, 35.345E-50}),
+            Arguments.of(new double[] {Double.MAX_VALUE, Double.MAX_VALUE}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {Double.MAX_VALUE, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, 1, 1}),
+            Arguments.of(new double[] {-Double.MAX_VALUE, -1, 1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -1}),
+            Arguments.of(new double[] {Double.MAX_VALUE, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, -Double.MAX_VALUE}),
+            Arguments.of(new double[] {1, 1, 1, -Double.MAX_VALUE})
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testMeanNonFinite(double[] values, double expected) {
+        Mean mean = Mean.create();
+        for (double value : values) {
+            mean.accept(value);
+        }
+        TestHelper.assertEquals(expected, mean.getAsDouble(), 2, () -> "mean 
non-finite");
+        TestHelper.assertEquals(expected, Mean.of(values).getAsDouble(), 2, () 
-> "of (values) non-finite");
+    }
+
+    @ParameterizedTest
+    @MethodSource(value = "testMeanNonFinite")
+    void testParallelStreamNonFinite(double[] values, double expected) {
+        double ans = Arrays.stream(values)
+                .parallel()
+                .collect(Mean::create, Mean::accept, Mean::combine)
+                .getAsDouble();
+        TestHelper.assertEquals(expected, ans, 2, () -> "parallel stream 
non-finite");

Review Comment:
   Assertions.assertEquals



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to