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

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


The following commit(s) were added to refs/heads/master by this push:
     new 171b29a  STATISTICS-81: Add integer sum implementation
171b29a is described below

commit 171b29a5780ccbbbf06ff3a08a5e63ec5dedd673
Author: Alex Herbert <aherb...@apache.org>
AuthorDate: Sat Dec 23 16:26:42 2023 +0000

    STATISTICS-81: Add integer sum implementation
---
 .../commons/statistics/descriptive/Int128.java     |  62 +++++++
 .../commons/statistics/descriptive/IntMath.java    |   4 +-
 .../commons/statistics/descriptive/IntSum.java     | 185 +++++++++++++++++++++
 .../commons/statistics/descriptive/LongSum.java    | 180 ++++++++++++++++++++
 .../commons/statistics/descriptive/UInt128.java    |   2 +-
 .../commons/statistics/descriptive/UInt192.java    |   4 +-
 .../commons/statistics/descriptive/Int128Test.java |  27 +++
 .../statistics/descriptive/IntMathTest.java        |   6 +-
 .../statistics/descriptive/IntMeanTest.java        |   2 +-
 .../{IntMeanTest.java => IntSumTest.java}          |  67 ++++----
 .../statistics/descriptive/LongMeanTest.java       |   2 +-
 .../{LongMeanTest.java => LongSumTest.java}        |  74 ++++-----
 12 files changed, 522 insertions(+), 93 deletions(-)

diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
index 5ae6357..bf0b99e 100644
--- 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
@@ -195,6 +195,44 @@ final class Int128 {
                 .putLong(h).putLong(l).array());
     }
 
+    /**
+     * Convert to a {@code double}.
+     *
+     * @return the value
+     */
+    double toDouble() {
+        long h = hi;
+        long l = lo;
+        // Special cases
+        if (h == 0) {
+            return l;
+        }
+        if (l == 0) {
+            return h * 0x1.0p64;
+        }
+        // Both h and l are non-zero and can be negated to a positive 
magnitude.
+        // Use the same logic as toBigInteger to create magnitude (h, l) and a 
sign.
+        int sign = 1;
+        if ((h ^ l) < 0) {
+            // Here we rearrange to [2^64 * (hi64-1)] + [2^64 - lo64].
+            if (h >= 0) {
+                h = h - 1;
+            } else {
+                // As above with negation
+                h = ~h; // -h - 1
+                l = -l;
+                sign = -1;
+            }
+        } else if (h < 0) {
+            // Invert negative values to create the equivalent positive 
magnitude.
+            h = -h;
+            l = -l;
+            sign = -1;
+        }
+        final double x = IntMath.uint128ToDouble(h, l);
+        return sign < 0 ? -x : x;
+    }
+
     /**
      * Convert to a double-double.
      *
@@ -211,6 +249,30 @@ final class Int128 {
         return DD.of(lo).add((hi & MASK32) * 0x1.0p64).add((hi >> 
Integer.SIZE) * 0x1.0p96);
     }
 
+    /**
+     * Convert to an {@code int}; throwing an exception if the value overflows 
an {@code int}.
+     *
+     * @return the value
+     * @throws ArithmeticException if the value overflows an {@code int}.
+     * @see Math#toIntExact(long)
+     */
+    int toIntExact() {
+        return Math.toIntExact(toLongExact());
+    }
+
+    /**
+     * Convert to a {@code long}; throwing an exception if the value overflows 
a {@code long}.
+     *
+     * @return the value
+     * @throws ArithmeticException if the value overflows a {@code long}.
+     */
+    long toLongExact() {
+        if (hi != 0) {
+            throw new ArithmeticException("long integer overflow");
+        }
+        return lo;
+    }
+
     /**
      * Return the lower 64-bits as a {@code long} value.
      *
diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
index 714fc41..2309750 100644
--- 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
@@ -212,7 +212,7 @@ final class IntMath {
             // Implicit conversion to a double.
             return lo;
         }
-        return uin128ToDouble(unsignedMultiplyHigh(x, y), lo);
+        return uint128ToDouble(unsignedMultiplyHigh(x, y), lo);
     }
 
     /**
@@ -222,7 +222,7 @@ final class IntMath {
      * @param lo Low 64-bits.
      * @return the double
      */
-    static double uin128ToDouble(long hi, long lo) {
+    static double uint128ToDouble(long hi, long lo) {
         // Require the representation:
         // 2^exp * mantissa / 2^53
         // The mantissa has an implied leading 1-bit.
diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
new file mode 100644
index 0000000..d1f4775
--- /dev/null
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSum.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+
+/**
+ * Returns the sum of the available values.
+ *
+ * <ul>
+ *   <li>The result is zero if no values are added.
+ * </ul>
+ *
+ * <p>This class uses an exact integer sum. It supports up to 2<sup>63</sup>
+ * values as the count \( n \) is maintained as a {@code long}. The exact sum 
is
+ * returned using {@link #getAsBigInteger()}. Methods that return {@code int} 
or
+ * {@code long} primitives will raise an exception if the result overflows.
+ * The {@code long} value is safe up to the maximum array length for any input
+ * {@code int[]} data. The {@code long} value can overflow when instances are 
combined.
+ *
+ * <p>Note that the implementation does not use {@code BigInteger} arithmetic; 
for
+ * performance the sum is computed using primitives to create a signed 128-bit 
integer.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link 
java.util.function.IntConsumer#accept(int) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it 
must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link 
java.util.function.IntConsumer#accept(int) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link 
java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @since 1.1
+ */
+public final class IntSum implements IntStatistic, 
StatisticAccumulator<IntSum> {
+    /** Sum of the values. */
+    private final Int128 sum;
+
+    /**
+     * Create an instance.
+     */
+    private IntSum() {
+        this(Int128.create());
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param sum Sum of the values.
+     */
+    private IntSum(Int128 sum) {
+        this.sum = sum;
+    }
+
+    /**
+     * Creates an instance.
+     *
+     * <p>The initial result is zero.
+     *
+     * @return {@code IntSum} instance.
+     */
+    public static IntSum create() {
+        return new IntSum();
+    }
+
+    /**
+     * Returns an instance populated using the input {@code values}.
+     *
+     * <p>When the input is an empty array, the result is zero.
+     *
+     * <p>The {@link #getAsLong()} result is valid for any input {@code int[]} 
length;
+     * the {@link #getAsInt()} result may overflow.
+     *
+     * @param values Values.
+     * @return {@code IntSum} instance.
+     */
+    public static IntSum of(int... values) {
+        // Sum of an array cannot exceed a 64-bit long
+        long s = 0;
+        for (final int x : values) {
+            s += x;
+        }
+        // Convert
+        return new IntSum(Int128.of(s));
+    }
+
+    /**
+     * Updates the state of the statistic to reflect the addition of {@code 
value}.
+     *
+     * @param value Value.
+     */
+    @Override
+    public void accept(int value) {
+        sum.add(value);
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Warning: This will raise an {@link ArithmeticException}
+     * if the result is not within the range {@code [-2^31, 2^31)}.
+     *
+     * @return sum of all values.
+     * @throws ArithmeticException if the {@code result} overflows an {@code 
int}
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public int getAsInt() {
+        return sum.toIntExact();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Warning: This will raise an {@link ArithmeticException}
+     * if the result is not within the range {@code [-2^63, 2^63)}.
+     *
+     * @return sum of all values.
+     * @throws ArithmeticException if the {@code result} overflows a {@code 
long}
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public long getAsLong() {
+        return sum.toLongExact();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Note that this conversion can lose information about the precision 
of the
+     * {@code BigInteger} value.
+     *
+     * @return sum of all values.
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public double getAsDouble() {
+        return sum.toDouble();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * @return sum of all values.
+     */
+    @Override
+    public BigInteger getAsBigInteger() {
+        return sum.toBigInteger();
+    }
+
+    @Override
+    public IntSum combine(IntSum other) {
+        sum.add(other.sum);
+        return this;
+    }
+}
diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
new file mode 100644
index 0000000..61efaa8
--- /dev/null
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSum.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+
+/**
+ * Returns the sum of the available values.
+ *
+ * <ul>
+ *   <li>The result is zero if no values are added.
+ * </ul>
+ *
+ * <p>This class uses an exact integer sum. It supports up to 2<sup>63</sup>
+ * values as the count \( n \) is maintained as a {@code long}. The exact sum 
is
+ * returned using {@link #getAsBigInteger()}. Methods that return {@code int} 
or
+ * {@code long} primitives will raise an exception if the result overflows.
+ * The {@code long} value is safe up to the maximum array length for any input
+ * {@code int[]} data. The {@code long} value can overflow when instances are 
combined.
+ *
+ * <p>Note that the implementation does not use {@code BigInteger} arithmetic; 
for
+ * performance the sum is computed using primitives to create a signed 128-bit 
integer.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link 
java.util.function.LongConsumer#accept(long) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it 
must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link 
java.util.function.LongConsumer#accept(long) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link 
java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @since 1.1
+ */
+public final class LongSum implements LongStatistic, 
StatisticAccumulator<LongSum> {
+    /** Sum of the values. */
+    private final Int128 sum;
+
+    /**
+     * Create an instance.
+     */
+    private LongSum() {
+        this(Int128.create());
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param sum Sum of the values.
+     */
+    private LongSum(Int128 sum) {
+        this.sum = sum;
+    }
+
+    /**
+     * Creates an instance.
+     *
+     * <p>The initial result is zero.
+     *
+     * @return {@code IntSum} instance.
+     */
+    public static LongSum create() {
+        return new LongSum();
+    }
+
+    /**
+     * Returns an instance populated using the input {@code values}.
+     *
+     * <p>When the input is an empty array, the result is zero.
+     *
+     * @param values Values.
+     * @return {@code IntSum} instance.
+     */
+    public static LongSum of(long... values) {
+        final Int128 s = Int128.create();
+        for (final long x : values) {
+            s.add(x);
+        }
+        return new LongSum(s);
+    }
+
+    /**
+     * Updates the state of the statistic to reflect the addition of {@code 
value}.
+     *
+     * @param value Value.
+     */
+    @Override
+    public void accept(long value) {
+        sum.add(value);
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Warning: This will raise an {@link ArithmeticException}
+     * if the result is not within the range {@code [-2^31, 2^31)}.
+     *
+     * @return sum of all values.
+     * @throws ArithmeticException if the {@code result} overflows an {@code 
int}
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public int getAsInt() {
+        return sum.toIntExact();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Warning: This will raise an {@link ArithmeticException}
+     * if the result is not within the range {@code [-2^63, 2^63)}.
+     *
+     * @return sum of all values.
+     * @throws ArithmeticException if the {@code result} overflows a {@code 
long}
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public long getAsLong() {
+        return sum.toLongExact();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * <p>Note that this conversion can lose information about the precision 
of the
+     * {@code BigInteger} value.
+     *
+     * @return sum of all values.
+     * @see #getAsBigInteger()
+     */
+    @Override
+    public double getAsDouble() {
+        return sum.toDouble();
+    }
+
+    /**
+     * Gets the sum of all input values.
+     *
+     * <p>When no values have been added, the result is zero.
+     *
+     * @return sum of all values.
+     */
+    @Override
+    public BigInteger getAsBigInteger() {
+        return sum.toBigInteger();
+    }
+
+    @Override
+    public LongSum combine(LongSum other) {
+        sum.add(other.sum);
+        return this;
+    }
+}
diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
index c4b421c..1d12a1e 100644
--- 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
@@ -197,7 +197,7 @@ final class UInt128 {
      * @return the value
      */
     double toDouble() {
-        return IntMath.uin128ToDouble(hi64(), lo64());
+        return IntMath.uint128ToDouble(hi64(), lo64());
     }
 
     /**
diff --git 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
index f390944..df46f84 100644
--- 
a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
+++ 
b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
@@ -211,11 +211,11 @@ final class UInt192 {
         final long m = mid64();
         final long l = lo64();
         if (h == 0) {
-            return IntMath.uin128ToDouble(m, l);
+            return IntMath.uint128ToDouble(m, l);
         }
         // For correct rounding we use a sticky bit to represent magnitude
         // lost from the low 64-bits. The result is scaled by 2^64.
-        return IntMath.uin128ToDouble(h, m | ((l == 0) ? 0 : 1)) * 0x1.0p64;
+        return IntMath.uint128ToDouble(h, m | ((l == 0) ? 0 : 1)) * 0x1.0p64;
     }
 
     /**
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
index 042ef10..7a5f298 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Test for {@link Int128}.
@@ -86,6 +87,7 @@ class Int128Test {
         }
         Assertions.assertEquals(expected, v.toBigInteger());
         // Check floating-point representation
+        Assertions.assertEquals(expected.doubleValue(), v.toDouble(), 
"double");
         TestHelper.assertEquals(new BigDecimal(expected), v.toDD(), 
0x1.0p-106, "DD");
     }
 
@@ -124,6 +126,7 @@ class Int128Test {
         Assertions.assertEquals(expected, x.toBigInteger(),
             () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
         // Check floating-point representation
+        Assertions.assertEquals(expected.doubleValue(), x.toDouble(), 
"double");
         TestHelper.assertEquals(new BigDecimal(expected), x.toDD(), 
0x1.0p-106, "DD");
         // Check self-addition
         expected = y.toBigInteger();
@@ -170,4 +173,28 @@ class Int128Test {
         RandomSource.XO_RO_SHI_RO_128_PP.create().longs(20).forEach(builder);
         return builder.build();
     }
+
+    @ParameterizedTest
+    @ValueSource(ints = {Integer.MAX_VALUE, Integer.MIN_VALUE})
+    void testToIntExact(int x) {
+        final Int128 v = Int128.of(x);
+        Assertions.assertEquals(x, v.toIntExact());
+        final int y = x < 0 ? -1 : 1;
+        v.add(y);
+        Assertions.assertThrows(ArithmeticException.class, () -> 
v.toIntExact());
+        v.add(-y);
+        Assertions.assertEquals(x, v.toIntExact());
+    }
+
+    @ParameterizedTest
+    @ValueSource(longs = {Long.MAX_VALUE, Long.MIN_VALUE})
+    void testToLongExact(long x) {
+        final Int128 v = Int128.of(x);
+        Assertions.assertEquals(x, v.toLongExact());
+        final int y = x < 0 ? -1 : 1;
+        v.add(y);
+        Assertions.assertThrows(ArithmeticException.class, () -> 
v.toLongExact());
+        v.add(-y);
+        Assertions.assertEquals(x, v.toLongExact());
+    }
 }
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
index 09721ff..c5785c0 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
@@ -113,15 +113,15 @@ class IntMathTest {
 
     @ParameterizedTest
     @MethodSource
-    void testUin128ToDouble(long a, long b) {
+    void testUint128ToDouble(long a, long b) {
         final BigInteger bi1 = toUnsignedBigInteger(a).shiftLeft(Long.SIZE);
         final BigInteger bi2 = toUnsignedBigInteger(b);
         final double x = bi1.add(bi2).doubleValue();
-        Assertions.assertEquals(x, IntMath.uin128ToDouble(a, b),
+        Assertions.assertEquals(x, IntMath.uint128ToDouble(a, b),
             () -> String.format("%s + %s", a, b));
     }
 
-    static Stream<Arguments> testUin128ToDouble() {
+    static Stream<Arguments> testUint128ToDouble() {
         final UniformRandomProvider rng = 
RandomSource.XO_RO_SHI_RO_128_PP.create();
         final Stream.Builder<Arguments> builder = Stream.builder();
         for (int i = 0; i < 100; i++) {
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
index 60d5449..5dde438 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
@@ -90,7 +90,7 @@ final class IntMeanTest extends BaseIntStatisticTest<IntMean> 
{
     }
 
     /**
-     * Test a large integer sums that overflow a {@code long}.
+     * Test large integer sums that overflow a {@code long}.
      * Overflow is created by repeat addition.
      *
      * <p>Note: Currently no check is made for overflow in the
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntSumTest.java
similarity index 57%
copy from 
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
copy to 
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntSumTest.java
index 60d5449..10c90dd 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntSumTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.statistics.descriptive;
 
+import java.math.BigInteger;
 import java.util.Arrays;
 import java.util.stream.Stream;
 import org.apache.commons.statistics.distribution.DoubleTolerance;
@@ -25,72 +26,61 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 
 /**
- * Test for {@link IntMean}.
+ * Test for {@link IntSum}.
  */
-final class IntMeanTest extends BaseIntStatisticTest<IntMean> {
+final class IntSumTest extends BaseIntStatisticTest<IntSum> {
 
     @Override
-    protected IntMean create() {
-        return IntMean.create();
+    protected ResultType getResultType() {
+        return ResultType.BIG_INTEGER;
     }
 
     @Override
-    protected IntMean create(int... values) {
-        return IntMean.of(values);
+    protected IntSum create() {
+        return IntSum.create();
+    }
+
+    @Override
+    protected IntSum create(int... values) {
+        return IntSum.of(values);
     }
 
     @Override
     protected DoubleStatistic createAsDoubleStatistic(int... values) {
-        return Mean.of(Arrays.stream(values).asDoubleStream().toArray());
+        return Sum.of(Arrays.stream(values).asDoubleStream().toArray());
     }
 
     @Override
     protected DoubleTolerance getToleranceAsDouble() {
-        // Large shifts in the rolling mean are not computed very accurately
-        return DoubleTolerances.relative(5e-8);
+        // Floating-point sum may be inexact.
+        // Currently the double sum matches on the standard test data.
+        return DoubleTolerances.equals();
     }
 
     @Override
     protected StatisticResult getEmptyValue() {
-        return createStatisticResult(Double.NaN);
+        // It does not matter that this returns a IntStatisticResult
+        // rather than a BigIntegerStatisticResult
+        return createStatisticResult(0);
     }
 
     @Override
     protected StatisticResult getExpectedValue(int[] values) {
         // Use the JDK as a reference implementation
-        final double x = Arrays.stream(values).average().orElse(Double.NaN);
+        final long x = Arrays.stream(values).asLongStream().sum();
         return createStatisticResult(x);
     }
 
-    @Override
-    protected DoubleTolerance getTolerance() {
-        return DoubleTolerances.equals();
-    }
-
     @Override
     protected Stream<StatisticTestData> streamTestData() {
-        final Stream.Builder<StatisticTestData> builder = Stream.builder();
-        builder.accept(addCase(Integer.MAX_VALUE - 1, Integer.MAX_VALUE));
-        builder.accept(addCase(Integer.MIN_VALUE + 1, Integer.MIN_VALUE));
-        final int[] a = new int[2 * 512 * 512];
-        Arrays.fill(a, 0, a.length / 2, 10);
-        Arrays.fill(a, a.length / 2, a.length, 1);
-        builder.accept(addReference(5.5, a));
-
-        // Same cases as for the DoubleStatistic Variance but the tolerance is 
exact
-        final DoubleTolerance tol = DoubleTolerances.equals();
-
-        // Python Numpy v1.25.1: numpy.mean
-        builder.accept(addReference(2.5, tol, 1, 2, 3, 4));
-        builder.accept(addReference(12.0, tol, 5, 9, 13, 14, 10, 12, 11, 15, 
19));
-        // R v4.3.1: mean(x)
-        builder.accept(addReference(5.5, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
-        builder.accept(addReference(8.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
10, 50));
-        return builder.build();
+        return Stream.of(
+            addCase(Integer.MAX_VALUE, 1, 2, 3, 4, Integer.MAX_VALUE),
+            addCase(Integer.MIN_VALUE, -1, -2, -3, -4, Integer.MIN_VALUE)
+        );
     }
 
     /**
-     * Test a large integer sums that overflow a {@code long}.
+     * Test large integer sums that overflow a {@code long}.
      * Overflow is created by repeat addition.
      *
      * <p>Note: Currently no check is made for overflow in the
@@ -105,12 +95,13 @@ final class IntMeanTest extends 
BaseIntStatisticTest<IntMean> {
         "-2147483648, -2147483647, 61",
     })
     void testLongOverflow(int x, int y, int exp) {
-        final IntMean s = IntMean.of(x, y);
-        final double mean = ((long) x + y) * 0.5;
+        final IntSum s = IntSum.of(x, y);
+        BigInteger sum = BigInteger.valueOf((long) x + y);
         for (int i = 0; i < exp; i++) {
             // Assumes the sum as a long will overflow
             s.combine(s);
-            Assertions.assertEquals(mean, s.getAsDouble());
+            sum = sum.shiftLeft(1);
+            Assertions.assertEquals(sum, s.getAsBigInteger());
         }
     }
 }
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
index 0d0f86b..849732e 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
@@ -97,7 +97,7 @@ final class LongMeanTest extends 
BaseLongStatisticTest<LongMean> {
     }
 
     /**
-     * Test a large integer sums that overflow a {@code long}.
+     * Test large integer sums that overflow a {@code long}.
      * Overflow is created by repeat addition.
      *
      * <p>Note: Currently no check is made for overflow in the
diff --git 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongSumTest.java
similarity index 55%
copy from 
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
copy to 
commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongSumTest.java
index 0d0f86b..9207112 100644
--- 
a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
+++ 
b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongSumTest.java
@@ -16,9 +16,7 @@
  */
 package org.apache.commons.statistics.descriptive;
 
-import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.math.MathContext;
 import java.util.Arrays;
 import java.util.stream.Stream;
 import org.apache.commons.statistics.distribution.DoubleTolerance;
@@ -28,76 +26,62 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
 
 /**
- * Test for {@link LongMean}.
+ * Test for {@link LongSum}.
  */
-final class LongMeanTest extends BaseLongStatisticTest<LongMean> {
+final class LongSumTest extends BaseLongStatisticTest<LongSum> {
 
     @Override
-    protected LongMean create() {
-        return LongMean.create();
+    protected ResultType getResultType() {
+        return ResultType.BIG_INTEGER;
     }
 
     @Override
-    protected LongMean create(long... values) {
-        return LongMean.of(values);
+    protected LongSum create() {
+        return LongSum.create();
     }
 
     @Override
-    protected StatisticResult getEmptyValue() {
-        return createStatisticResult(Double.NaN);
+    protected LongSum create(long... values) {
+        return LongSum.of(values);
     }
 
     @Override
     protected DoubleStatistic createAsDoubleStatistic(long... values) {
-        return Mean.of(Arrays.stream(values).asDoubleStream().toArray());
+        return Sum.of(Arrays.stream(values).asDoubleStream().toArray());
     }
 
     @Override
     protected DoubleTolerance getToleranceAsDouble() {
-        // Data with large shifts in the rolling mean is not computed very 
accurately
-        return DoubleTolerances.relative(5e-8);
+        // Floating-point sum may be inexact.
+        // Currently the double sum matches on the standard test data.
+        return DoubleTolerances.equals();
+    }
+
+    @Override
+    protected StatisticResult getEmptyValue() {
+        // It does not matter that this returns a IntStatisticResult
+        // rather than a BigIntegerStatisticResult
+        return createStatisticResult(0);
     }
 
     @Override
     protected StatisticResult getExpectedValue(long[] values) {
-        final BigInteger sum = Arrays.stream(values)
+        final BigInteger x = Arrays.stream(values)
             .mapToObj(BigInteger::valueOf)
             .reduce(BigInteger.ZERO, BigInteger::add);
-        final double x = new BigDecimal(sum)
-            .divide(BigDecimal.valueOf(values.length), MathContext.DECIMAL128)
-            .doubleValue();
         return createStatisticResult(x);
     }
 
-    @Override
-    protected DoubleTolerance getTolerance() {
-        return DoubleTolerances.equals();
-    }
-
     @Override
     protected Stream<StatisticTestData> streamTestData() {
-        final Stream.Builder<StatisticTestData> builder = Stream.builder();
-        builder.accept(addCase(Long.MAX_VALUE - 1, Long.MAX_VALUE));
-        builder.accept(addCase(Long.MIN_VALUE + 1, Long.MIN_VALUE));
-        final long[] a = new long[2 * 512 * 512];
-        Arrays.fill(a, 0, a.length / 2, 10);
-        Arrays.fill(a, a.length / 2, a.length, 1);
-        builder.accept(addReference(5.5, a));
-
-        // Same cases as for the DoubleStatistic Variance but the tolerance is 
exact
-        final DoubleTolerance tol = DoubleTolerances.equals();
-
-        // Python Numpy v1.25.1: numpy.mean
-        builder.accept(addReference(2.5, tol, 1, 2, 3, 4));
-        builder.accept(addReference(12.0, tol, 5, 9, 13, 14, 10, 12, 11, 15, 
19));
-        // R v4.3.1: mean(x)
-        builder.accept(addReference(5.5, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
-        builder.accept(addReference(8.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
10, 50));
-        return builder.build();
+        return Stream.of(
+            addCase(Long.MAX_VALUE, 1, 2, 3, 4, Long.MAX_VALUE),
+            addCase(Long.MIN_VALUE, -1, -2, -3, -4, Long.MIN_VALUE)
+        );
     }
 
     /**
-     * Test a large integer sums that overflow a {@code long}.
+     * Test large integer sums that overflow a {@code long}.
      * Overflow is created by repeat addition.
      *
      * <p>Note: Currently no check is made for overflow in the
@@ -112,13 +96,13 @@ final class LongMeanTest extends 
BaseLongStatisticTest<LongMean> {
         "-9223372036854775808, -9223372036854775807, 61",
     })
     void testLongOverflow(long x, long y, int exp) {
-        final LongMean s = LongMean.of(x, y);
-        final double mean = BigInteger.valueOf(x)
-            .add(BigInteger.valueOf(y)).doubleValue() * 0.5;
+        final LongSum s = LongSum.of(x, y);
+        BigInteger sum = BigInteger.valueOf(x).add(BigInteger.valueOf(y));
         for (int i = 0; i < exp; i++) {
             // Assumes the sum as a long will overflow
             s.combine(s);
-            Assertions.assertEquals(mean, s.getAsDouble());
+            sum = sum.shiftLeft(1);
+            Assertions.assertEquals(sum, s.getAsBigInteger());
         }
     }
 }


Reply via email to