You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2024/01/01 10:06:21 UTC

(commons-statistics) 02/02: STATISTICS-81: Add aggregate statistics for int and long

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

commit 3afb93d2b17182c5d4cf07839c6fe2e39eb3da54
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Sun Dec 31 23:16:31 2023 +0000

    STATISTICS-81: Add aggregate statistics for int and long
---
 .../descriptive/BigIntegerStatisticResult.java     |   0
 .../statistics/descriptive/DoubleStatistics.java   | 129 +-----
 .../statistics/descriptive/IntStatisticResult.java |   0
 .../{DoubleStatistics.java => IntStatistics.java}  | 448 ++++++++++-----------
 .../commons/statistics/descriptive/IntSum.java     |  11 +
 .../statistics/descriptive/IntSumOfSquares.java    |  11 +
 .../descriptive/LongStatisticResult.java           |   0
 .../{DoubleStatistics.java => LongStatistics.java} | 426 +++++++++-----------
 .../commons/statistics/descriptive/LongSum.java    |  11 +
 .../statistics/descriptive/LongSumOfSquares.java   |  11 +
 .../commons/statistics/descriptive/Statistics.java | 218 ++++++++++
 .../descriptive/DoubleStatisticsTest.java          |  17 +-
 ...eStatisticsTest.java => IntStatisticsTest.java} | 394 ++++++++++--------
 ...StatisticsTest.java => LongStatisticsTest.java} | 398 ++++++++++--------
 .../statistics/descriptive/StatisticsTest.java     | 116 ++++++
 .../commons/statistics/descriptive/TestHelper.java |  80 +++-
 src/conf/checkstyle/checkstyle-suppressions.xml    |   4 +-
 src/conf/pmd/pmd-ruleset.xml                       |   3 +-
 18 files changed, 1332 insertions(+), 945 deletions(-)

diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/BigIntegerStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
index d77bc64..3bdd073 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
@@ -34,25 +34,10 @@ import java.util.function.Function;
  * @since 1.1
  */
 public final class DoubleStatistics implements DoubleConsumer {
-    /** A no-operation double consumer. This is exposed for testing. */
-    static final DoubleConsumer NOOP = new DoubleConsumer() {
-        @Override
-        public void accept(double value) {
-            // Do nothing
-        }
-
-        @Override
-        public DoubleConsumer andThen(DoubleConsumer after) {
-            // Delegate to the after consumer
-            return after;
-        }
-    };
     /** Error message for non configured statistics. */
     private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
     /** Error message for an unsupported statistic. */
     private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
-    /** Error message for an incompatible statistics. */
-    private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
 
     /** Count of values recorded. */
     private long count;
@@ -264,28 +249,7 @@ public final class DoubleStatistics implements DoubleConsumer {
         this.sumOfSquares = sumOfSquares;
         this.sumOfLogs = sumOfLogs;
         this.config = config;
-        consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
-    }
-
-    /**
-     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
-     * consumer.
-     *
-     * @param consumers Consumers.
-     * @return a composed consumer
-     */
-    private static DoubleConsumer compose(DoubleConsumer... consumers) {
-        DoubleConsumer action = NOOP;
-        for (final DoubleConsumer consumer : consumers) {
-            if (consumer != null) {
-                action = action.andThen(consumer);
-            }
-        }
-        if (action == NOOP) {
-            // This should not be possible
-            throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
-        }
-        return action;
+        consumer = Statistics.compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
     }
 
     /**
@@ -453,7 +417,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * Gets the value of the specified {@code statistic}.
      *
      * @param statistic Statistic.
-     * @return the double
+     * @return the value
      * @throws IllegalArgumentException if the {@code statistic} is not supported
      * @see #isSupported(Statistic)
      * @see #getSupplier(Statistic)
@@ -631,86 +595,25 @@ public final class DoubleStatistics implements DoubleConsumer {
      */
     public DoubleStatistics combine(DoubleStatistics other) {
         // Check compatibility
-        checkNullOrElseOtherNonNull(min, other.min);
-        checkNullOrElseOtherNonNull(max, other.max);
-        checkNullOrElseOtherNonNull(sum, other.sum);
-        checkNullOrElseOtherNonNull(product, other.product);
-        checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
-        checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
-        checkNullOrElseOtherIsAssignable(moment, other.moment);
+        Statistics.checkCombineCompatible(min, other.min);
+        Statistics.checkCombineCompatible(max, other.max);
+        Statistics.checkCombineCompatible(sum, other.sum);
+        Statistics.checkCombineCompatible(product, other.product);
+        Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+        Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+        Statistics.checkCombineAssignable(moment, other.moment);
         // Combine
         count += other.count;
-        combine(min, other.min);
-        combine(max, other.max);
-        combine(sum, other.sum);
-        combine(product, other.product);
-        combine(sumOfSquares, other.sumOfSquares);
-        combine(sumOfLogs, other.sumOfLogs);
-        combineMoment(moment, other.moment);
+        Statistics.combine(min, other.min);
+        Statistics.combine(max, other.max);
+        Statistics.combine(sum, other.sum);
+        Statistics.combine(product, other.product);
+        Statistics.combine(sumOfSquares, other.sumOfSquares);
+        Statistics.combine(sumOfLogs, other.sumOfLogs);
+        Statistics.combineMoment(moment, other.moment);
         return this;
     }
 
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must also be non-{@code null}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherNonNull(Object a, Object b) {
-        if (a != null && b == null) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must be run-time assignable to the same class as {@code a}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
-        if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}.
-     *
-     * @param <T> {@link DoubleStatistic} being accumulated.
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
-        if (a != null) {
-            a.combine(b);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
-     * to the same class as LHS.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void combineMoment(FirstMoment a, FirstMoment b) {
-        // Avoid reflection and use the simpler instanceof
-        if (a instanceof SumOfFourthDeviations) {
-            ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
-        } else if (a instanceof SumOfCubedDeviations) {
-            ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
-        } else if (a instanceof SumOfSquaredDeviations) {
-            ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
-        } else if (a != null) {
-            a.combine(b);
-        }
-    }
-
     /**
      * Sets the statistics configuration.
      *
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
similarity index 61%
copy from commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
copy to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
index d77bc64..35b9248 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntStatistics.java
@@ -16,14 +16,15 @@
  */
 package org.apache.commons.statistics.descriptive;
 
+import java.math.BigInteger;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.DoubleConsumer;
-import java.util.function.DoubleSupplier;
 import java.util.function.Function;
+import java.util.function.IntConsumer;
 
 /**
- * Statistics for {@code double} values.
+ * Statistics for {@code int} values.
  *
  * <p>This class provides combinations of individual statistic implementations in the
  * {@code org.apache.commons.statistics.descriptive} package.
@@ -33,69 +34,55 @@ import java.util.function.Function;
  *
  * @since 1.1
  */
-public final class DoubleStatistics implements DoubleConsumer {
-    /** A no-operation double consumer. This is exposed for testing. */
-    static final DoubleConsumer NOOP = new DoubleConsumer() {
-        @Override
-        public void accept(double value) {
-            // Do nothing
-        }
-
-        @Override
-        public DoubleConsumer andThen(DoubleConsumer after) {
-            // Delegate to the after consumer
-            return after;
-        }
-    };
+public final class IntStatistics implements IntConsumer {
     /** Error message for non configured statistics. */
     private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
     /** Error message for an unsupported statistic. */
     private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
-    /** Error message for an incompatible statistics. */
-    private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
 
     /** Count of values recorded. */
     private long count;
     /** The consumer of values. */
-    private final DoubleConsumer consumer;
-    /** The {@link Min} implementation. */
-    private final Min min;
-    /** The {@link Max} implementation. */
-    private final Max max;
-    /** The moment implementation. May be any instance of {@link FirstMoment}. */
+    private final IntConsumer consumer;
+    /** The {@link IntMin} implementation. */
+    private final IntMin min;
+    /** The {@link IntMax} implementation. */
+    private final IntMax max;
+    /** The moment implementation. May be any instance of {@link FirstMoment}.
+     * This implementation uses only the third and fourth moments. */
     private final FirstMoment moment;
-    /** The {@link Sum} implementation. */
-    private final Sum sum;
+    /** The {@link IntSum} implementation. */
+    private final IntSum sum;
     /** The {@link Product} implementation. */
     private final Product product;
-    /** The {@link SumOfSquares} implementation. */
-    private final SumOfSquares sumOfSquares;
+    /** The {@link IntSumOfSquares} implementation. */
+    private final IntSumOfSquares sumOfSquares;
     /** The {@link SumOfLogs} implementation. */
     private final SumOfLogs sumOfLogs;
     /** Configuration options for computation of statistics. */
     private StatisticsConfiguration config;
 
     /**
-     * A builder for {@link DoubleStatistics}.
+     * A builder for {@link IntStatistics}.
      */
     public static final class Builder {
         /** An empty double array. */
-        private static final double[] NO_VALUES = {};
+        private static final int[] NO_VALUES = {};
 
-        /** The {@link Min} constructor. */
-        private Function<double[], Min> min;
-        /** The {@link Max} constructor. */
-        private Function<double[], Max> max;
+        /** The {@link IntMin} constructor. */
+        private Function<int[], IntMin> min;
+        /** The {@link IntMax} constructor. */
+        private Function<int[], IntMax> max;
         /** The moment constructor. May return any instance of {@link FirstMoment}. */
-        private Function<double[], FirstMoment> moment;
-        /** The {@link Sum} constructor. */
-        private Function<double[], Sum> sum;
+        private Function<int[], FirstMoment> moment;
+        /** The {@link IntSum} constructor. */
+        private Function<int[], IntSum> sum;
         /** The {@link Product} constructor. */
-        private Function<double[], Product> product;
-        /** The {@link SumOfSquares} constructor. */
-        private Function<double[], SumOfSquares> sumOfSquares;
+        private Function<int[], Product> product;
+        /** The {@link IntSumOfSquares} constructor. */
+        private Function<int[], IntSumOfSquares> sumOfSquares;
         /** The {@link SumOfLogs} constructor. */
-        private Function<double[], SumOfLogs> sumOfLogs;
+        private Function<int[], SumOfLogs> sumOfLogs;
         /** The order of the moment. It corresponds to the power computed by the {@link FirstMoment}
          * instance constructed by {@link #moment}. This should only be increased from the default
          * of zero (corresponding to no moment computation). */
@@ -126,13 +113,10 @@ public final class DoubleStatistics implements DoubleConsumer {
                 createMoment(4);
                 break;
             case MAX:
-                max = Max::of;
-                break;
-            case MEAN:
-                createMoment(1);
+                max = IntMax::of;
                 break;
             case MIN:
-                min = Min::of;
+                min = IntMin::of;
                 break;
             case PRODUCT:
                 product = Product::of;
@@ -142,13 +126,15 @@ public final class DoubleStatistics implements DoubleConsumer {
                 break;
             case STANDARD_DEVIATION:
             case VARIANCE:
-                createMoment(2);
+                sum = IntSum::of;
+                sumOfSquares = IntSumOfSquares::of;
                 break;
+            case MEAN:
             case SUM:
-                sum = Sum::of;
+                sum = IntSum::of;
                 break;
             case SUM_OF_SQUARES:
-                sumOfSquares = SumOfSquares::of;
+                sumOfSquares = IntSumOfSquares::of;
                 break;
             default:
                 throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
@@ -158,7 +144,7 @@ public final class DoubleStatistics implements DoubleConsumer {
 
         /**
          * Creates the moment constructor for the specified {@code order},
-         * e.g. order=2 is sum of squared deviations.
+         * e.g. order=3 is sum of cubed deviations.
          *
          * @param order Order.
          */
@@ -167,13 +153,9 @@ public final class DoubleStatistics implements DoubleConsumer {
                 momentOrder = order;
                 if (order == 4) {
                     moment = SumOfFourthDeviations::of;
-                } else if (order == 3) {
-                    moment = SumOfCubedDeviations::of;
-                } else if (order == 2) {
-                    moment = SumOfSquaredDeviations::of;
                 } else {
-                    // Assume order == 1
-                    moment = FirstMoment::of;
+                    // Assume order == 3
+                    moment = SumOfCubedDeviations::of;
                 }
             }
         }
@@ -191,27 +173,27 @@ public final class DoubleStatistics implements DoubleConsumer {
         }
 
         /**
-         * Builds a {@code DoubleStatistics} instance.
+         * Builds a {@code IntStatistics} instance.
          *
-         * @return {@code DoubleStatistics} instance.
+         * @return {@code IntStatistics} instance.
          */
-        public DoubleStatistics build() {
+        public IntStatistics build() {
             return build(NO_VALUES);
         }
 
         /**
-         * Builds a {@code DoubleStatistics} instance using the input {@code values}.
+         * Builds a {@code IntStatistics} instance using the input {@code values}.
          *
-         * <p>Note: {@code DoubleStatistics} computed using
-         * {@link DoubleStatistics#accept(double) accept} may be
+         * <p>Note: {@code IntStatistics} computed using
+         * {@link IntStatistics#accept(int) accept} may be
          * different from this instance.
          *
          * @param values Values.
-         * @return {@code DoubleStatistics} instance.
+         * @return {@code IntStatistics} instance.
          */
-        public DoubleStatistics build(double... values) {
+        public IntStatistics build(int... values) {
             Objects.requireNonNull(values, "values");
-            return new DoubleStatistics(
+            return new IntStatistics(
                 values.length,
                 create(min, values),
                 create(max, values),
@@ -231,7 +213,7 @@ public final class DoubleStatistics implements DoubleConsumer {
          * @param values Values
          * @return the instance
          */
-        private static <T> T create(Function<double[], T> constructor, double[] values) {
+        private static <T> T create(Function<int[], T> constructor, int[] values) {
             if (constructor != null) {
                 return constructor.apply(values);
             }
@@ -243,18 +225,18 @@ public final class DoubleStatistics implements DoubleConsumer {
      * Create an instance.
      *
      * @param count Count of values.
-     * @param min Min implementation.
-     * @param max Max implementation.
+     * @param min IntMin implementation.
+     * @param max IntMax implementation.
      * @param moment Moment implementation.
-     * @param sum Sum implementation.
+     * @param sum IntSum implementation.
      * @param product Product implementation.
      * @param sumOfSquares Sum of squares implementation.
      * @param sumOfLogs Sum of logs implementation.
      * @param config Statistics configuration.
      */
-    DoubleStatistics(long count, Min min, Max max, FirstMoment moment, Sum sum,
-                     Product product, SumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
-                     StatisticsConfiguration config) {
+    IntStatistics(long count, IntMin min, IntMax max, FirstMoment moment, IntSum sum,
+                  Product product, IntSumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
+                  StatisticsConfiguration config) {
         this.count = count;
         this.min = min;
         this.max = max;
@@ -264,28 +246,25 @@ public final class DoubleStatistics implements DoubleConsumer {
         this.sumOfSquares = sumOfSquares;
         this.sumOfLogs = sumOfLogs;
         this.config = config;
-        consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
+        // The final consumer should never be null as the builder is created
+        // with at least one statistic.
+        consumer = Statistics.compose(min, max, sum, sumOfSquares,
+                                      composeAsInt(moment, product, sumOfLogs));
     }
 
     /**
-     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
-     * consumer.
+     * Chain the {@code consumers} into a single composite {@code IntConsumer}.
+     * Ignore any {@code null} consumer.
      *
      * @param consumers Consumers.
-     * @return a composed consumer
+     * @return a composed consumer (or null)
      */
-    private static DoubleConsumer compose(DoubleConsumer... consumers) {
-        DoubleConsumer action = NOOP;
-        for (final DoubleConsumer consumer : consumers) {
-            if (consumer != null) {
-                action = action.andThen(consumer);
-            }
+    private static IntConsumer composeAsInt(DoubleConsumer... consumers) {
+        final DoubleConsumer c = Statistics.compose(consumers);
+        if (c != null) {
+            return c::accept;
         }
-        if (action == NOOP) {
-            // This should not be possible
-            throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
-        }
-        return action;
+        return null;
     }
 
     /**
@@ -298,7 +277,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the instance
      * @throws IllegalArgumentException if there are no {@code statistics} to compute.
      */
-    public static DoubleStatistics of(Statistic... statistics) {
+    public static IntStatistics of(Statistic... statistics) {
         return builder(statistics).build();
     }
 
@@ -307,10 +286,10 @@ public final class DoubleStatistics implements DoubleConsumer {
      * populated using the input {@code values}.
      *
      * <p>Use this method to create an instance populated with a (variable) array of
-     * {@code double[]} data:
+     * {@code int[]} data:
      *
      * <pre>
-     * DoubleStatistics stats = DoubleStatistics.of(
+     * IntStatistics stats = IntStatistics.of(
      *     EnumSet.of(Statistic.MIN, Statistic.MAX),
      *     1, 1, 2, 3, 5, 8, 13);
      * </pre>
@@ -320,7 +299,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the instance
      * @throws IllegalArgumentException if there are no {@code statistics} to compute.
      */
-    public static DoubleStatistics of(Set<Statistic> statistics, double... values) {
+    public static IntStatistics of(Set<Statistic> statistics, int... values) {
         if (statistics.isEmpty()) {
             throw new IllegalArgumentException(NO_CONFIGURED_STATISTICS);
         }
@@ -333,28 +312,28 @@ public final class DoubleStatistics implements DoubleConsumer {
      * Returns a new builder configured to create instances to compute the specified
      * {@code statistics}.
      *
-     * <p>Use this method to create an instance populated with an array of {@code double[]}
-     * data using the {@link Builder#build(double...)} method:
+     * <p>Use this method to create an instance populated with an array of {@code int[]}
+     * data using the {@link Builder#build(int...)} method:
      *
      * <pre>
-     * double[] data = ...
-     * DoubleStatistics stats = DoubleStatistics.builder(
+     * int[] data = ...
+     * IntStatistics stats = IntStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE)
      *     .build(data);
      * </pre>
      *
-     * <p>The builder can be used to create multiple instances of {@link DoubleStatistics}
-     * to be used in parallel, or on separate arrays of {@code double[]} data. These may
-     * be {@link #combine(DoubleStatistics) combined}. For example:
+     * <p>The builder can be used to create multiple instances of {@link IntStatistics}
+     * to be used in parallel, or on separate arrays of {@code int[]} data. These may
+     * be {@link #combine(IntStatistics) combined}. For example:
      *
      * <pre>
-     * double[][] data = ...
-     * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+     * int[][] data = ...
+     * IntStatistics.Builder builder = IntStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
-     * DoubleStatistics stats = Arrays.stream(data)
+     * IntStatistics stats = Arrays.stream(data)
      *     .parallel()
      *     .map(builder::build)
-     *     .reduce(DoubleStatistics::combine)
+     *     .reduce(IntStatistics::combine)
      *     .get();
      * </pre>
      *
@@ -362,16 +341,16 @@ public final class DoubleStatistics implements DoubleConsumer {
      * use on multiple data:
      *
      * <pre>{@code
-     * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+     * IntStatistics.Builder builder = IntStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
-     * Collector<double[], DoubleStatistics, DoubleStatistics> collector =
+     * Collector<int[], IntStatistics, IntStatistics> collector =
      *     Collector.of(builder::build,
      *                  (s, d) -> s.combine(builder.build(d)),
-     *                  DoubleStatistics::combine);
+     *                  IntStatistics::combine);
      *
      * // Repeated
-     * double[][] data = ...
-     * DoubleStatistics stats = Arrays.stream(data).collect(collector);
+     * int[][] data = ...
+     * IntStatistics stats = Arrays.stream(data).collect(collector);
      * }</pre>
      *
      * @param statistics Statistics to compute.
@@ -395,7 +374,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param value Value.
      */
     @Override
-    public void accept(double value) {
+    public void accept(int value) {
         count++;
         consumer.accept(value);
     }
@@ -417,7 +396,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param statistic Statistic.
      * @return {@code true} if supported
      * @throws NullPointerException if the {@code statistic} is {@code null}
-     * @see #get(Statistic)
+     * @see #getResult(Statistic)
      */
     public boolean isSupported(Statistic statistic) {
         // Check for the appropriate underlying implementation
@@ -429,8 +408,6 @@ public final class DoubleStatistics implements DoubleConsumer {
             return moment instanceof SumOfFourthDeviations;
         case MAX:
             return max != null;
-        case MEAN:
-            return moment != null;
         case MIN:
             return min != null;
         case PRODUCT:
@@ -439,7 +416,8 @@ public final class DoubleStatistics implements DoubleConsumer {
             return moment instanceof SumOfCubedDeviations;
         case STANDARD_DEVIATION:
         case VARIANCE:
-            return moment instanceof SumOfSquaredDeviations;
+            return sum != null && sumOfSquares != null;
+        case MEAN:
         case SUM:
             return sum != null;
         case SUM_OF_SQUARES:
@@ -450,24 +428,87 @@ public final class DoubleStatistics implements DoubleConsumer {
     }
 
     /**
-     * Gets the value of the specified {@code statistic}.
+     * Gets the value of the specified {@code statistic} as a {@code double}.
      *
      * @param statistic Statistic.
-     * @return the double
+     * @return the value
      * @throws IllegalArgumentException if the {@code statistic} is not supported
      * @see #isSupported(Statistic)
-     * @see #getSupplier(Statistic)
+     * @see #getResult(Statistic)
      */
-    public double get(Statistic statistic) {
-        return getSupplier(statistic).getAsDouble();
+    public double getAsDouble(Statistic statistic) {
+        return getResult(statistic).getAsDouble();
+    }
+
+    /**
+     * Gets the value of the specified {@code statistic} as an {@code int}.
+     *
+     * <p>Use this method to access the {@code int} result for exact integer statistics,
+     * for example {@link Statistic#MIN}.
+     *
+     * <p>Note: This method may throw an {@link ArithmeticException} if the result
+     * overflows an {@code int}.
+     *
+     * @param statistic Statistic.
+     * @return the value
+     * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @throws ArithmeticException if the {@code result} overflows an {@code int} or is not
+     * finite
+     * @see #isSupported(Statistic)
+     * @see #getResult(Statistic)
+     */
+    public int getAsInt(Statistic statistic) {
+        return getResult(statistic).getAsInt();
+    }
+
+    /**
+     * Gets the value of the specified {@code statistic} as a {@code long}.
+     *
+     * <p>Use this method to access the {@code long} result for exact integer statistics,
+     * for example {@link Statistic#SUM} for a {@link #getCount() count} less than or equal to
+     *2<sup>32</sup>.
+     *
+     * <p>Note: This method may throw an {@link ArithmeticException} if the result
+     * overflows an {@code long}.
+     *
+     * @param statistic Statistic.
+     * @return the value
+     * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @throws ArithmeticException if the {@code result} overflows an {@code long} or is not
+     * finite
+     * @see #isSupported(Statistic)
+     * @see #getResult(Statistic)
+     */
+    public long getAsLong(Statistic statistic) {
+        return getResult(statistic).getAsLong();
+    }
+
+    /**
+     * Gets the value of the specified {@code statistic} as a {@code BigInteger}.
+     *
+     * <p>Use this method to access the {@code BigInteger} result for exact integer statistics,
+     * for example {@link Statistic#SUM_OF_SQUARES}.
+     *
+     * <p>Note: This method may throw an {@link ArithmeticException} if the result
+     * is not finite.
+     *
+     * @param statistic Statistic.
+     * @return the value
+     * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @throws ArithmeticException if the {@code result} is not finite
+     * @see #isSupported(Statistic)
+     * @see #getResult(Statistic)
+     */
+    public BigInteger getAsBigInteger(Statistic statistic) {
+        return getResult(statistic).getAsBigInteger();
     }
 
     /**
      * Gets a supplier for the value of the specified {@code statistic}.
      *
      * <p>The returned function will supply the correct result after
-     * calls to {@link #accept(double) accept} or
-     * {@link #combine(DoubleStatistics) combine} further values into
+     * calls to {@link #accept(int) accept} or
+     * {@link #combine(IntStatistics) combine} further values into
      * {@code this} instance.
      *
      * <p>This method can be used to perform a one-time look-up of the statistic
@@ -477,15 +518,15 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the supplier
      * @throws IllegalArgumentException if the {@code statistic} is not supported
      * @see #isSupported(Statistic)
-     * @see #get(Statistic)
+     * @see #getAsDouble(Statistic)
      */
-    public DoubleSupplier getSupplier(Statistic statistic) {
+    public StatisticResult getResult(Statistic statistic) {
         // Locate the implementation.
         // Statistics that wrap an underlying implementation are created in methods.
-        // The return argument should be a method reference and not an instance
-        // of DoubleStatistic. This ensures the statistic implementation cannot
-        // be updated with new values by casting the result and calling accept(double).
-        DoubleSupplier stat = null;
+        // The return argument should be an interface reference and not an instance
+        // of IntStatistic. This ensures the statistic implementation cannot
+        // be updated with new values by casting the result and calling accept(int).
+        StatisticResult stat = null;
         switch (statistic) {
         case GEOMETRIC_MEAN:
             stat = getGeometricMean();
@@ -494,16 +535,16 @@ public final class DoubleStatistics implements DoubleConsumer {
             stat = getKurtosis();
             break;
         case MAX:
-            stat = max;
+            stat = Statistics.getResultAsIntOrNull(max);
             break;
         case MEAN:
             stat = getMean();
             break;
         case MIN:
-            stat = min;
+            stat = Statistics.getResultAsIntOrNull(min);
             break;
         case PRODUCT:
-            stat = product;
+            stat = Statistics.getResultAsDoubleOrNull(product);
             break;
         case SKEWNESS:
             stat = getSkewness();
@@ -512,13 +553,13 @@ public final class DoubleStatistics implements DoubleConsumer {
             stat = getStandardDeviation();
             break;
         case SUM:
-            stat = sum;
+            stat = Statistics.getResultAsBigIntegerOrNull(sum);
             break;
         case SUM_OF_LOGS:
-            stat = sumOfLogs;
+            stat = Statistics.getResultAsDoubleOrNull(sumOfLogs);
             break;
         case SUM_OF_SQUARES:
-            stat = sumOfSquares;
+            stat = Statistics.getResultAsBigIntegerOrNull(sumOfSquares);
             break;
         case VARIANCE:
             stat = getVariance();
@@ -527,9 +568,7 @@ public final class DoubleStatistics implements DoubleConsumer {
             break;
         }
         if (stat != null) {
-            return stat instanceof DoubleStatistic ?
-                ((DoubleStatistic) stat)::getAsDouble :
-                stat;
+            return stat;
         }
         throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
     }
@@ -539,7 +578,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a geometric mean supplier (or null if unsupported)
      */
-    private DoubleSupplier getGeometricMean() {
+    private StatisticResult getGeometricMean() {
         if (sumOfLogs != null) {
             // Return a function that has access to the count and sumOfLogs
             return () -> GeometricMean.computeGeometricMean(count, sumOfLogs);
@@ -552,7 +591,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a kurtosis supplier (or null if unsupported)
      */
-    private DoubleSupplier getKurtosis() {
+    private StatisticResult getKurtosis() {
         if (moment instanceof SumOfFourthDeviations) {
             return new Kurtosis((SumOfFourthDeviations) moment)
                 .setBiased(config.isBiased())::getAsDouble;
@@ -565,10 +604,11 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a mean supplier (or null if unsupported)
      */
-    private DoubleSupplier getMean() {
-        if (moment != null) {
-            // Special case where wrapping with a Mean is not required
-            return moment::getFirstMoment;
+    private StatisticResult getMean() {
+        if (sum != null) {
+            // Return a function that has access to the count and sum
+            final Int128 s = sum.getSum();
+            return () -> IntMean.computeMean(s, count);
         }
         return null;
     }
@@ -578,7 +618,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a skewness supplier (or null if unsupported)
      */
-    private DoubleSupplier getSkewness() {
+    private StatisticResult getSkewness() {
         if (moment instanceof SumOfCubedDeviations) {
             return new Skewness((SumOfCubedDeviations) moment)
                 .setBiased(config.isBiased())::getAsDouble;
@@ -591,12 +631,8 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a standard deviation supplier (or null if unsupported)
      */
-    private DoubleSupplier getStandardDeviation() {
-        if (moment instanceof SumOfSquaredDeviations) {
-            return new StandardDeviation((SumOfSquaredDeviations) moment)
-                .setBiased(config.isBiased())::getAsDouble;
-        }
-        return null;
+    private StatisticResult getStandardDeviation() {
+        return getVarianceOrStd(true);
     }
 
     /**
@@ -604,10 +640,23 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a variance supplier (or null if unsupported)
      */
-    private DoubleSupplier getVariance() {
-        if (moment instanceof SumOfSquaredDeviations) {
-            return new Variance((SumOfSquaredDeviations) moment)
-                .setBiased(config.isBiased())::getAsDouble;
+    private StatisticResult getVariance() {
+        return getVarianceOrStd(false);
+    }
+
+    /**
+     * Gets the variance or standard deviation.
+     *
+     * @param std Flag to control if the statistic is the standard deviation.
+     * @return a variance/standard deviation supplier (or null if unsupported)
+     */
+    private StatisticResult getVarianceOrStd(boolean std) {
+        if (sum != null && sumOfSquares != null) {
+            // Return a function that has access to the count, sum and sum of squares
+            final Int128 s = sum.getSum();
+            final UInt128 ss = sumOfSquares.getSumOfSquares();
+            final boolean biased = config.isBiased();
+            return () -> IntVariance.computeVarianceOrStd(ss, s, count, biased, std);
         }
         return null;
     }
@@ -629,94 +678,33 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return {@code this} instance after combining {@code other}.
      * @throws IllegalArgumentException if the {@code other} is not compatible
      */
-    public DoubleStatistics combine(DoubleStatistics other) {
+    public IntStatistics combine(IntStatistics other) {
         // Check compatibility
-        checkNullOrElseOtherNonNull(min, other.min);
-        checkNullOrElseOtherNonNull(max, other.max);
-        checkNullOrElseOtherNonNull(sum, other.sum);
-        checkNullOrElseOtherNonNull(product, other.product);
-        checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
-        checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
-        checkNullOrElseOtherIsAssignable(moment, other.moment);
+        Statistics.checkCombineCompatible(min, other.min);
+        Statistics.checkCombineCompatible(max, other.max);
+        Statistics.checkCombineCompatible(sum, other.sum);
+        Statistics.checkCombineCompatible(product, other.product);
+        Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+        Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+        Statistics.checkCombineAssignable(moment, other.moment);
         // Combine
         count += other.count;
-        combine(min, other.min);
-        combine(max, other.max);
-        combine(sum, other.sum);
-        combine(product, other.product);
-        combine(sumOfSquares, other.sumOfSquares);
-        combine(sumOfLogs, other.sumOfLogs);
-        combineMoment(moment, other.moment);
+        Statistics.combine(min, other.min);
+        Statistics.combine(max, other.max);
+        Statistics.combine(sum, other.sum);
+        Statistics.combine(product, other.product);
+        Statistics.combine(sumOfSquares, other.sumOfSquares);
+        Statistics.combine(sumOfLogs, other.sumOfLogs);
+        Statistics.combineMoment(moment, other.moment);
         return this;
     }
 
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must also be non-{@code null}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherNonNull(Object a, Object b) {
-        if (a != null && b == null) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must be run-time assignable to the same class as {@code a}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
-        if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}.
-     *
-     * @param <T> {@link DoubleStatistic} being accumulated.
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
-        if (a != null) {
-            a.combine(b);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
-     * to the same class as LHS.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void combineMoment(FirstMoment a, FirstMoment b) {
-        // Avoid reflection and use the simpler instanceof
-        if (a instanceof SumOfFourthDeviations) {
-            ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
-        } else if (a instanceof SumOfCubedDeviations) {
-            ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
-        } else if (a instanceof SumOfSquaredDeviations) {
-            ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
-        } else if (a != null) {
-            a.combine(b);
-        }
-    }
-
     /**
      * Sets the statistics configuration.
      *
      * <p>These options only control the final computation of statistics. The configuration
      * will not affect compatibility between instances during a
-     * {@link #combine(DoubleStatistics) combine} operation.
+     * {@link #combine(IntStatistics) combine} operation.
      *
      * <p>Note: These options will affect any future computation of statistics. Supplier functions
      * that have been previously created will not be updated with the new configuration.
@@ -724,9 +712,9 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param v Value.
      * @return {@code this} instance
      * @throws NullPointerException if the value is null
-     * @see #getSupplier(Statistic)
+     * @see #getResult(Statistic)
      */
-    public DoubleStatistics setConfiguration(StatisticsConfiguration v) {
+    public IntStatistics setConfiguration(StatisticsConfiguration v) {
         config = Objects.requireNonNull(v);
         return this;
     }
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
index 6b3b5d4..5857ce1 100644
--- 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
@@ -105,6 +105,17 @@ public final class IntSum implements IntStatistic, StatisticAccumulator<IntSum>
         return new IntSum(Int128.of(s));
     }
 
+    /**
+     * Gets the sum.
+     *
+     * <p>This is package private for use in {@link IntStatistics}.
+     *
+     * @return the sum
+     */
+    Int128 getSum() {
+        return sum;
+    }
+
     /**
      * Updates the state of the statistic to reflect the addition of {@code value}.
      *
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
index 31dd481..9905f28 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntSumOfSquares.java
@@ -126,6 +126,17 @@ public final class IntSumOfSquares implements IntStatistic, StatisticAccumulator
         return new IntSumOfSquares(UInt128.of(ss));
     }
 
+    /**
+     * Gets the sum of squares.
+     *
+     * <p>This is package private for use in {@link IntStatistics}.
+     *
+     * @return the sum of squares
+     */
+    UInt128 getSumOfSquares() {
+        return sumSq;
+    }
+
     /**
      * Updates the state of the statistic to reflect the addition of {@code value}.
      *
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
similarity index 100%
rename from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
rename to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatisticResult.java
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
similarity index 61%
copy from commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
copy to commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
index d77bc64..3255daf 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/DoubleStatistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongStatistics.java
@@ -16,14 +16,15 @@
  */
 package org.apache.commons.statistics.descriptive;
 
+import java.math.BigInteger;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.DoubleConsumer;
-import java.util.function.DoubleSupplier;
 import java.util.function.Function;
+import java.util.function.LongConsumer;
 
 /**
- * Statistics for {@code double} values.
+ * Statistics for {@code long} values.
  *
  * <p>This class provides combinations of individual statistic implementations in the
  * {@code org.apache.commons.statistics.descriptive} package.
@@ -33,69 +34,55 @@ import java.util.function.Function;
  *
  * @since 1.1
  */
-public final class DoubleStatistics implements DoubleConsumer {
-    /** A no-operation double consumer. This is exposed for testing. */
-    static final DoubleConsumer NOOP = new DoubleConsumer() {
-        @Override
-        public void accept(double value) {
-            // Do nothing
-        }
-
-        @Override
-        public DoubleConsumer andThen(DoubleConsumer after) {
-            // Delegate to the after consumer
-            return after;
-        }
-    };
+public final class LongStatistics implements LongConsumer {
     /** Error message for non configured statistics. */
     private static final String NO_CONFIGURED_STATISTICS = "No configured statistics";
     /** Error message for an unsupported statistic. */
     private static final String UNSUPPORTED_STATISTIC = "Unsupported statistic: ";
-    /** Error message for an incompatible statistics. */
-    private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
 
     /** Count of values recorded. */
     private long count;
     /** The consumer of values. */
-    private final DoubleConsumer consumer;
-    /** The {@link Min} implementation. */
-    private final Min min;
-    /** The {@link Max} implementation. */
-    private final Max max;
-    /** The moment implementation. May be any instance of {@link FirstMoment}. */
+    private final LongConsumer consumer;
+    /** The {@link LongMin} implementation. */
+    private final LongMin min;
+    /** The {@link LongMax} implementation. */
+    private final LongMax max;
+    /** The moment implementation. May be any instance of {@link FirstMoment}.
+     * This implementation uses only the third and fourth moments. */
     private final FirstMoment moment;
-    /** The {@link Sum} implementation. */
-    private final Sum sum;
+    /** The {@link LongSum} implementation. */
+    private final LongSum sum;
     /** The {@link Product} implementation. */
     private final Product product;
-    /** The {@link SumOfSquares} implementation. */
-    private final SumOfSquares sumOfSquares;
+    /** The {@link LongSumOfSquares} implementation. */
+    private final LongSumOfSquares sumOfSquares;
     /** The {@link SumOfLogs} implementation. */
     private final SumOfLogs sumOfLogs;
     /** Configuration options for computation of statistics. */
     private StatisticsConfiguration config;
 
     /**
-     * A builder for {@link DoubleStatistics}.
+     * A builder for {@link LongStatistics}.
      */
     public static final class Builder {
         /** An empty double array. */
-        private static final double[] NO_VALUES = {};
+        private static final long[] NO_VALUES = {};
 
-        /** The {@link Min} constructor. */
-        private Function<double[], Min> min;
-        /** The {@link Max} constructor. */
-        private Function<double[], Max> max;
+        /** The {@link LongMin} constructor. */
+        private Function<long[], LongMin> min;
+        /** The {@link LongMax} constructor. */
+        private Function<long[], LongMax> max;
         /** The moment constructor. May return any instance of {@link FirstMoment}. */
-        private Function<double[], FirstMoment> moment;
-        /** The {@link Sum} constructor. */
-        private Function<double[], Sum> sum;
+        private Function<long[], FirstMoment> moment;
+        /** The {@link LongSum} constructor. */
+        private Function<long[], LongSum> sum;
         /** The {@link Product} constructor. */
-        private Function<double[], Product> product;
-        /** The {@link SumOfSquares} constructor. */
-        private Function<double[], SumOfSquares> sumOfSquares;
+        private Function<long[], Product> product;
+        /** The {@link LongSumOfSquares} constructor. */
+        private Function<long[], LongSumOfSquares> sumOfSquares;
         /** The {@link SumOfLogs} constructor. */
-        private Function<double[], SumOfLogs> sumOfLogs;
+        private Function<long[], SumOfLogs> sumOfLogs;
         /** The order of the moment. It corresponds to the power computed by the {@link FirstMoment}
          * instance constructed by {@link #moment}. This should only be increased from the default
          * of zero (corresponding to no moment computation). */
@@ -126,13 +113,10 @@ public final class DoubleStatistics implements DoubleConsumer {
                 createMoment(4);
                 break;
             case MAX:
-                max = Max::of;
-                break;
-            case MEAN:
-                createMoment(1);
+                max = LongMax::of;
                 break;
             case MIN:
-                min = Min::of;
+                min = LongMin::of;
                 break;
             case PRODUCT:
                 product = Product::of;
@@ -142,13 +126,15 @@ public final class DoubleStatistics implements DoubleConsumer {
                 break;
             case STANDARD_DEVIATION:
             case VARIANCE:
-                createMoment(2);
+                sum = LongSum::of;
+                sumOfSquares = LongSumOfSquares::of;
                 break;
+            case MEAN:
             case SUM:
-                sum = Sum::of;
+                sum = LongSum::of;
                 break;
             case SUM_OF_SQUARES:
-                sumOfSquares = SumOfSquares::of;
+                sumOfSquares = LongSumOfSquares::of;
                 break;
             default:
                 throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
@@ -158,7 +144,7 @@ public final class DoubleStatistics implements DoubleConsumer {
 
         /**
          * Creates the moment constructor for the specified {@code order},
-         * e.g. order=2 is sum of squared deviations.
+         * e.g. order=3 is sum of cubed deviations.
          *
          * @param order Order.
          */
@@ -167,13 +153,9 @@ public final class DoubleStatistics implements DoubleConsumer {
                 momentOrder = order;
                 if (order == 4) {
                     moment = SumOfFourthDeviations::of;
-                } else if (order == 3) {
-                    moment = SumOfCubedDeviations::of;
-                } else if (order == 2) {
-                    moment = SumOfSquaredDeviations::of;
                 } else {
-                    // Assume order == 1
-                    moment = FirstMoment::of;
+                    // Assume order == 3
+                    moment = SumOfCubedDeviations::of;
                 }
             }
         }
@@ -191,27 +173,27 @@ public final class DoubleStatistics implements DoubleConsumer {
         }
 
         /**
-         * Builds a {@code DoubleStatistics} instance.
+         * Builds a {@code LongStatistics} instance.
          *
-         * @return {@code DoubleStatistics} instance.
+         * @return {@code LongStatistics} instance.
          */
-        public DoubleStatistics build() {
+        public LongStatistics build() {
             return build(NO_VALUES);
         }
 
         /**
-         * Builds a {@code DoubleStatistics} instance using the input {@code values}.
+         * Builds a {@code LongStatistics} instance using the input {@code values}.
          *
-         * <p>Note: {@code DoubleStatistics} computed using
-         * {@link DoubleStatistics#accept(double) accept} may be
+         * <p>Note: {@code LongStatistics} computed using
+         * {@link LongStatistics#accept(long) accept} may be
          * different from this instance.
          *
          * @param values Values.
-         * @return {@code DoubleStatistics} instance.
+         * @return {@code LongStatistics} instance.
          */
-        public DoubleStatistics build(double... values) {
+        public LongStatistics build(long... values) {
             Objects.requireNonNull(values, "values");
-            return new DoubleStatistics(
+            return new LongStatistics(
                 values.length,
                 create(min, values),
                 create(max, values),
@@ -231,7 +213,7 @@ public final class DoubleStatistics implements DoubleConsumer {
          * @param values Values
          * @return the instance
          */
-        private static <T> T create(Function<double[], T> constructor, double[] values) {
+        private static <T> T create(Function<long[], T> constructor, long[] values) {
             if (constructor != null) {
                 return constructor.apply(values);
             }
@@ -243,18 +225,18 @@ public final class DoubleStatistics implements DoubleConsumer {
      * Create an instance.
      *
      * @param count Count of values.
-     * @param min Min implementation.
-     * @param max Max implementation.
+     * @param min LongMin implementation.
+     * @param max LongMax implementation.
      * @param moment Moment implementation.
-     * @param sum Sum implementation.
+     * @param sum LongSum implementation.
      * @param product Product implementation.
      * @param sumOfSquares Sum of squares implementation.
      * @param sumOfLogs Sum of logs implementation.
      * @param config Statistics configuration.
      */
-    DoubleStatistics(long count, Min min, Max max, FirstMoment moment, Sum sum,
-                     Product product, SumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
-                     StatisticsConfiguration config) {
+    LongStatistics(long count, LongMin min, LongMax max, FirstMoment moment, LongSum sum,
+                  Product product, LongSumOfSquares sumOfSquares, SumOfLogs sumOfLogs,
+                  StatisticsConfiguration config) {
         this.count = count;
         this.min = min;
         this.max = max;
@@ -264,28 +246,25 @@ public final class DoubleStatistics implements DoubleConsumer {
         this.sumOfSquares = sumOfSquares;
         this.sumOfLogs = sumOfLogs;
         this.config = config;
-        consumer = compose(min, max, moment, sum, product, sumOfSquares, sumOfLogs);
+        // The final consumer should never be null as the builder is created
+        // with at least one statistic.
+        consumer = Statistics.compose(min, max, sum, sumOfSquares,
+                                      composeAsLong(moment, product, sumOfLogs));
     }
 
     /**
-     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
-     * consumer.
+     * Chain the {@code consumers} into a single composite {@code LongConsumer}.
+     * Ignore any {@code null} consumer.
      *
      * @param consumers Consumers.
-     * @return a composed consumer
+     * @return a composed consumer (or null)
      */
-    private static DoubleConsumer compose(DoubleConsumer... consumers) {
-        DoubleConsumer action = NOOP;
-        for (final DoubleConsumer consumer : consumers) {
-            if (consumer != null) {
-                action = action.andThen(consumer);
-            }
-        }
-        if (action == NOOP) {
-            // This should not be possible
-            throw new IllegalStateException(NO_CONFIGURED_STATISTICS + ": Please file a bug report");
+    private static LongConsumer composeAsLong(DoubleConsumer... consumers) {
+        final DoubleConsumer c = Statistics.compose(consumers);
+        if (c != null) {
+            return c::accept;
         }
-        return action;
+        return null;
     }
 
     /**
@@ -298,7 +277,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the instance
      * @throws IllegalArgumentException if there are no {@code statistics} to compute.
      */
-    public static DoubleStatistics of(Statistic... statistics) {
+    public static LongStatistics of(Statistic... statistics) {
         return builder(statistics).build();
     }
 
@@ -307,10 +286,10 @@ public final class DoubleStatistics implements DoubleConsumer {
      * populated using the input {@code values}.
      *
      * <p>Use this method to create an instance populated with a (variable) array of
-     * {@code double[]} data:
+     * {@code long[]} data:
      *
      * <pre>
-     * DoubleStatistics stats = DoubleStatistics.of(
+     * LongStatistics stats = LongStatistics.of(
      *     EnumSet.of(Statistic.MIN, Statistic.MAX),
      *     1, 1, 2, 3, 5, 8, 13);
      * </pre>
@@ -320,7 +299,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the instance
      * @throws IllegalArgumentException if there are no {@code statistics} to compute.
      */
-    public static DoubleStatistics of(Set<Statistic> statistics, double... values) {
+    public static LongStatistics of(Set<Statistic> statistics, long... values) {
         if (statistics.isEmpty()) {
             throw new IllegalArgumentException(NO_CONFIGURED_STATISTICS);
         }
@@ -333,28 +312,28 @@ public final class DoubleStatistics implements DoubleConsumer {
      * Returns a new builder configured to create instances to compute the specified
      * {@code statistics}.
      *
-     * <p>Use this method to create an instance populated with an array of {@code double[]}
-     * data using the {@link Builder#build(double...)} method:
+     * <p>Use this method to create an instance populated with an array of {@code long[]}
+     * data using the {@link Builder#build(long...)} method:
      *
      * <pre>
-     * double[] data = ...
-     * DoubleStatistics stats = DoubleStatistics.builder(
+     * long[] data = ...
+     * LongStatistics stats = LongStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE)
      *     .build(data);
      * </pre>
      *
-     * <p>The builder can be used to create multiple instances of {@link DoubleStatistics}
-     * to be used in parallel, or on separate arrays of {@code double[]} data. These may
-     * be {@link #combine(DoubleStatistics) combined}. For example:
+     * <p>The builder can be used to create multiple instances of {@link LongStatistics}
+     * to be used in parallel, or on separate arrays of {@code long[]} data. These may
+     * be {@link #combine(LongStatistics) combined}. For example:
      *
      * <pre>
-     * double[][] data = ...
-     * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+     * long[][] data = ...
+     * LongStatistics.Builder builder = LongStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
-     * DoubleStatistics stats = Arrays.stream(data)
+     * LongStatistics stats = Arrays.stream(data)
      *     .parallel()
      *     .map(builder::build)
-     *     .reduce(DoubleStatistics::combine)
+     *     .reduce(LongStatistics::combine)
      *     .get();
      * </pre>
      *
@@ -362,16 +341,16 @@ public final class DoubleStatistics implements DoubleConsumer {
      * use on multiple data:
      *
      * <pre>{@code
-     * DoubleStatistics.Builder builder = DoubleStatistics.builder(
+     * LongStatistics.Builder builder = LongStatistics.builder(
      *     Statistic.MIN, Statistic.MAX, Statistic.VARIANCE);
-     * Collector<double[], DoubleStatistics, DoubleStatistics> collector =
+     * Collector<long[], LongStatistics, LongStatistics> collector =
      *     Collector.of(builder::build,
      *                  (s, d) -> s.combine(builder.build(d)),
-     *                  DoubleStatistics::combine);
+     *                  LongStatistics::combine);
      *
      * // Repeated
-     * double[][] data = ...
-     * DoubleStatistics stats = Arrays.stream(data).collect(collector);
+     * long[][] data = ...
+     * LongStatistics stats = Arrays.stream(data).collect(collector);
      * }</pre>
      *
      * @param statistics Statistics to compute.
@@ -395,7 +374,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param value Value.
      */
     @Override
-    public void accept(double value) {
+    public void accept(long value) {
         count++;
         consumer.accept(value);
     }
@@ -417,7 +396,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param statistic Statistic.
      * @return {@code true} if supported
      * @throws NullPointerException if the {@code statistic} is {@code null}
-     * @see #get(Statistic)
+     * @see #getResult(Statistic)
      */
     public boolean isSupported(Statistic statistic) {
         // Check for the appropriate underlying implementation
@@ -429,8 +408,6 @@ public final class DoubleStatistics implements DoubleConsumer {
             return moment instanceof SumOfFourthDeviations;
         case MAX:
             return max != null;
-        case MEAN:
-            return moment != null;
         case MIN:
             return min != null;
         case PRODUCT:
@@ -439,7 +416,8 @@ public final class DoubleStatistics implements DoubleConsumer {
             return moment instanceof SumOfCubedDeviations;
         case STANDARD_DEVIATION:
         case VARIANCE:
-            return moment instanceof SumOfSquaredDeviations;
+            return sum != null && sumOfSquares != null;
+        case MEAN:
         case SUM:
             return sum != null;
         case SUM_OF_SQUARES:
@@ -450,24 +428,65 @@ public final class DoubleStatistics implements DoubleConsumer {
     }
 
     /**
-     * Gets the value of the specified {@code statistic}.
+     * Gets the value of the specified {@code statistic} as a {@code double}.
+     *
+     * @param statistic Statistic.
+     * @return the value
+     * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @see #isSupported(Statistic)
+     * @see #getResult(Statistic)
+     */
+    public double getAsDouble(Statistic statistic) {
+        return getResult(statistic).getAsDouble();
+    }
+
+    /**
+     * Gets the value of the specified {@code statistic} as a {@code long}.
+     *
+     * <p>Use this method to access the {@code long} result for exact integer statistics,
+     * for example {@link Statistic#MIN}.
+     *
+     * <p>Note: This method may throw an {@link ArithmeticException} if the result
+     * overflows an {@code long}.
+     *
+     * @param statistic Statistic.
+     * @return the value
+     * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @throws ArithmeticException if the {@code result} overflows an {@code long} or is not
+     * finite
+     * @see #isSupported(Statistic)
+     * @see #getResult(Statistic)
+     */
+    public long getAsLong(Statistic statistic) {
+        return getResult(statistic).getAsLong();
+    }
+
+    /**
+     * Gets the value of the specified {@code statistic} as a {@code BigInteger}.
+     *
+     * <p>Use this method to access the {@code BigInteger} result for exact integer statistics,
+     * for example {@link Statistic#SUM_OF_SQUARES}.
+     *
+     * <p>Note: This method may throw an {@link ArithmeticException} if the result
+     * is not finite.
      *
      * @param statistic Statistic.
-     * @return the double
+     * @return the value
      * @throws IllegalArgumentException if the {@code statistic} is not supported
+     * @throws ArithmeticException if the {@code result} is not finite
      * @see #isSupported(Statistic)
-     * @see #getSupplier(Statistic)
+     * @see #getResult(Statistic)
      */
-    public double get(Statistic statistic) {
-        return getSupplier(statistic).getAsDouble();
+    public BigInteger getAsBigInteger(Statistic statistic) {
+        return getResult(statistic).getAsBigInteger();
     }
 
     /**
      * Gets a supplier for the value of the specified {@code statistic}.
      *
      * <p>The returned function will supply the correct result after
-     * calls to {@link #accept(double) accept} or
-     * {@link #combine(DoubleStatistics) combine} further values into
+     * calls to {@link #accept(long) accept} or
+     * {@link #combine(LongStatistics) combine} further values into
      * {@code this} instance.
      *
      * <p>This method can be used to perform a one-time look-up of the statistic
@@ -477,15 +496,15 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return the supplier
      * @throws IllegalArgumentException if the {@code statistic} is not supported
      * @see #isSupported(Statistic)
-     * @see #get(Statistic)
+     * @see #getAsDouble(Statistic)
      */
-    public DoubleSupplier getSupplier(Statistic statistic) {
+    public StatisticResult getResult(Statistic statistic) {
         // Locate the implementation.
         // Statistics that wrap an underlying implementation are created in methods.
-        // The return argument should be a method reference and not an instance
-        // of DoubleStatistic. This ensures the statistic implementation cannot
-        // be updated with new values by casting the result and calling accept(double).
-        DoubleSupplier stat = null;
+        // The return argument should be an interface reference and not an instance
+        // of LongStatistic. This ensures the statistic implementation cannot
+        // be updated with new values by casting the result and calling accept(long).
+        StatisticResult stat = null;
         switch (statistic) {
         case GEOMETRIC_MEAN:
             stat = getGeometricMean();
@@ -494,16 +513,16 @@ public final class DoubleStatistics implements DoubleConsumer {
             stat = getKurtosis();
             break;
         case MAX:
-            stat = max;
+            stat = Statistics.getResultAsLongOrNull(max);
             break;
         case MEAN:
             stat = getMean();
             break;
         case MIN:
-            stat = min;
+            stat = Statistics.getResultAsLongOrNull(min);
             break;
         case PRODUCT:
-            stat = product;
+            stat = Statistics.getResultAsDoubleOrNull(product);
             break;
         case SKEWNESS:
             stat = getSkewness();
@@ -512,13 +531,13 @@ public final class DoubleStatistics implements DoubleConsumer {
             stat = getStandardDeviation();
             break;
         case SUM:
-            stat = sum;
+            stat = Statistics.getResultAsBigIntegerOrNull(sum);
             break;
         case SUM_OF_LOGS:
-            stat = sumOfLogs;
+            stat = Statistics.getResultAsDoubleOrNull(sumOfLogs);
             break;
         case SUM_OF_SQUARES:
-            stat = sumOfSquares;
+            stat = Statistics.getResultAsBigIntegerOrNull(sumOfSquares);
             break;
         case VARIANCE:
             stat = getVariance();
@@ -527,9 +546,7 @@ public final class DoubleStatistics implements DoubleConsumer {
             break;
         }
         if (stat != null) {
-            return stat instanceof DoubleStatistic ?
-                ((DoubleStatistic) stat)::getAsDouble :
-                stat;
+            return stat;
         }
         throw new IllegalArgumentException(UNSUPPORTED_STATISTIC + statistic);
     }
@@ -539,7 +556,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a geometric mean supplier (or null if unsupported)
      */
-    private DoubleSupplier getGeometricMean() {
+    private StatisticResult getGeometricMean() {
         if (sumOfLogs != null) {
             // Return a function that has access to the count and sumOfLogs
             return () -> GeometricMean.computeGeometricMean(count, sumOfLogs);
@@ -552,7 +569,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a kurtosis supplier (or null if unsupported)
      */
-    private DoubleSupplier getKurtosis() {
+    private StatisticResult getKurtosis() {
         if (moment instanceof SumOfFourthDeviations) {
             return new Kurtosis((SumOfFourthDeviations) moment)
                 .setBiased(config.isBiased())::getAsDouble;
@@ -565,10 +582,11 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a mean supplier (or null if unsupported)
      */
-    private DoubleSupplier getMean() {
-        if (moment != null) {
-            // Special case where wrapping with a Mean is not required
-            return moment::getFirstMoment;
+    private StatisticResult getMean() {
+        if (sum != null) {
+            // Return a function that has access to the count and sum
+            final Int128 s = sum.getSum();
+            return () -> LongMean.computeMean(s, count);
         }
         return null;
     }
@@ -578,7 +596,7 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a skewness supplier (or null if unsupported)
      */
-    private DoubleSupplier getSkewness() {
+    private StatisticResult getSkewness() {
         if (moment instanceof SumOfCubedDeviations) {
             return new Skewness((SumOfCubedDeviations) moment)
                 .setBiased(config.isBiased())::getAsDouble;
@@ -591,12 +609,8 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a standard deviation supplier (or null if unsupported)
      */
-    private DoubleSupplier getStandardDeviation() {
-        if (moment instanceof SumOfSquaredDeviations) {
-            return new StandardDeviation((SumOfSquaredDeviations) moment)
-                .setBiased(config.isBiased())::getAsDouble;
-        }
-        return null;
+    private StatisticResult getStandardDeviation() {
+        return getVarianceOrStd(true);
     }
 
     /**
@@ -604,10 +618,23 @@ public final class DoubleStatistics implements DoubleConsumer {
      *
      * @return a variance supplier (or null if unsupported)
      */
-    private DoubleSupplier getVariance() {
-        if (moment instanceof SumOfSquaredDeviations) {
-            return new Variance((SumOfSquaredDeviations) moment)
-                .setBiased(config.isBiased())::getAsDouble;
+    private StatisticResult getVariance() {
+        return getVarianceOrStd(false);
+    }
+
+    /**
+     * Gets the variance or standard deviation.
+     *
+     * @param std Flag to control if the statistic is the standard deviation.
+     * @return a variance/standard deviation supplier (or null if unsupported)
+     */
+    private StatisticResult getVarianceOrStd(boolean std) {
+        if (sum != null && sumOfSquares != null) {
+            // Return a function that has access to the count, sum and sum of squares
+            final Int128 s = sum.getSum();
+            final UInt192 ss = sumOfSquares.getSumOfSquares();
+            final boolean biased = config.isBiased();
+            return () -> LongVariance.computeVarianceOrStd(ss, s, count, biased, std);
         }
         return null;
     }
@@ -629,94 +656,33 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @return {@code this} instance after combining {@code other}.
      * @throws IllegalArgumentException if the {@code other} is not compatible
      */
-    public DoubleStatistics combine(DoubleStatistics other) {
+    public LongStatistics combine(LongStatistics other) {
         // Check compatibility
-        checkNullOrElseOtherNonNull(min, other.min);
-        checkNullOrElseOtherNonNull(max, other.max);
-        checkNullOrElseOtherNonNull(sum, other.sum);
-        checkNullOrElseOtherNonNull(product, other.product);
-        checkNullOrElseOtherNonNull(sumOfSquares, other.sumOfSquares);
-        checkNullOrElseOtherNonNull(sumOfLogs, other.sumOfLogs);
-        checkNullOrElseOtherIsAssignable(moment, other.moment);
+        Statistics.checkCombineCompatible(min, other.min);
+        Statistics.checkCombineCompatible(max, other.max);
+        Statistics.checkCombineCompatible(sum, other.sum);
+        Statistics.checkCombineCompatible(product, other.product);
+        Statistics.checkCombineCompatible(sumOfSquares, other.sumOfSquares);
+        Statistics.checkCombineCompatible(sumOfLogs, other.sumOfLogs);
+        Statistics.checkCombineAssignable(moment, other.moment);
         // Combine
         count += other.count;
-        combine(min, other.min);
-        combine(max, other.max);
-        combine(sum, other.sum);
-        combine(product, other.product);
-        combine(sumOfSquares, other.sumOfSquares);
-        combine(sumOfLogs, other.sumOfLogs);
-        combineMoment(moment, other.moment);
+        Statistics.combine(min, other.min);
+        Statistics.combine(max, other.max);
+        Statistics.combine(sum, other.sum);
+        Statistics.combine(product, other.product);
+        Statistics.combine(sumOfSquares, other.sumOfSquares);
+        Statistics.combine(sumOfLogs, other.sumOfLogs);
+        Statistics.combineMoment(moment, other.moment);
         return this;
     }
 
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must also be non-{@code null}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherNonNull(Object a, Object b) {
-        if (a != null && b == null) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
-     * argument {@code b} must be run-time assignable to the same class as {@code a}.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void checkNullOrElseOtherIsAssignable(Object a, Object b) {
-        if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
-            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}.
-     *
-     * @param <T> {@link DoubleStatistic} being accumulated.
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> void combine(T a, T b) {
-        if (a != null) {
-            a.combine(b);
-        }
-    }
-
-    /**
-     * If the left-hand side argument {@code a} is non-{@code null}, combined it with the
-     * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
-     * to the same class as LHS.
-     *
-     * @param a LHS.
-     * @param b RHS.
-     */
-    private static void combineMoment(FirstMoment a, FirstMoment b) {
-        // Avoid reflection and use the simpler instanceof
-        if (a instanceof SumOfFourthDeviations) {
-            ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
-        } else if (a instanceof SumOfCubedDeviations) {
-            ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
-        } else if (a instanceof SumOfSquaredDeviations) {
-            ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
-        } else if (a != null) {
-            a.combine(b);
-        }
-    }
-
     /**
      * Sets the statistics configuration.
      *
      * <p>These options only control the final computation of statistics. The configuration
      * will not affect compatibility between instances during a
-     * {@link #combine(DoubleStatistics) combine} operation.
+     * {@link #combine(LongStatistics) combine} operation.
      *
      * <p>Note: These options will affect any future computation of statistics. Supplier functions
      * that have been previously created will not be updated with the new configuration.
@@ -724,9 +690,9 @@ public final class DoubleStatistics implements DoubleConsumer {
      * @param v Value.
      * @return {@code this} instance
      * @throws NullPointerException if the value is null
-     * @see #getSupplier(Statistic)
+     * @see #getResult(Statistic)
      */
-    public DoubleStatistics setConfiguration(StatisticsConfiguration v) {
+    public LongStatistics setConfiguration(StatisticsConfiguration v) {
         config = Objects.requireNonNull(v);
         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
index 0c2779d..101e6ad 100644
--- 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
@@ -98,6 +98,17 @@ public final class LongSum implements LongStatistic, StatisticAccumulator<LongSu
         return new LongSum(s);
     }
 
+    /**
+     * Gets the sum.
+     *
+     * <p>This is package private for use in {@link LongStatistics}.
+     *
+     * @return the sum
+     */
+    Int128 getSum() {
+        return sum;
+    }
+
     /**
      * Updates the state of the statistic to reflect the addition of {@code value}.
      *
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
index 74ea71b..fc0b7c0 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongSumOfSquares.java
@@ -101,6 +101,17 @@ public final class LongSumOfSquares implements LongStatistic, StatisticAccumulat
         return new LongSumOfSquares(ss);
     }
 
+    /**
+     * Gets the sum of squares.
+     *
+     * <p>This is package private for use in {@link IntStatistics}.
+     *
+     * @return the sum of squares
+     */
+    UInt192 getSumOfSquares() {
+        return sumSq;
+    }
+
     /**
      * Updates the state of the statistic to reflect the addition of {@code value}.
      *
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
index 20a5846..34bda8f 100644
--- a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Statistics.java
@@ -24,6 +24,51 @@ import java.util.function.LongConsumer;
  * Utility methods for statistics.
  */
 final class Statistics {
+    /** A no-operation double consumer. This is exposed for testing. */
+    static final DoubleConsumer DOUBLE_NOOP = new DoubleConsumer() {
+        @Override
+        public void accept(double value) {
+            // Do nothing
+        }
+
+        @Override
+        public DoubleConsumer andThen(DoubleConsumer after) {
+            // Delegate to the after consumer
+            return after;
+        }
+    };
+
+    /** A no-operation int consumer. This is exposed for testing. */
+    static final IntConsumer INT_NOOP = new IntConsumer() {
+        @Override
+        public void accept(int value) {
+            // Do nothing
+        }
+
+        @Override
+        public IntConsumer andThen(IntConsumer after) {
+            // Delegate to the after consumer
+            return after;
+        }
+    };
+
+    /** A no-operation long consumer. This is exposed for testing. */
+    static final LongConsumer LONG_NOOP = new LongConsumer() {
+        @Override
+        public void accept(long value) {
+            // Do nothing
+        }
+
+        @Override
+        public LongConsumer andThen(LongConsumer after) {
+            // Delegate to the after consumer
+            return after;
+        }
+    };
+
+    /** Error message for an incompatible statistics. */
+    private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";
+
     /** No instances. */
     private Statistics() {}
 
@@ -123,4 +168,177 @@ final class Statistics {
         final double meanPrecision = 1e-15 * m1;
         return m2 <= meanPrecision * meanPrecision;
     }
+
+    /**
+     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+     * consumer. Returns {@code null} if all arguments are {@code null}.
+     *
+     * @param consumers Consumers.
+     * @return a composed consumer (or null)
+     */
+    static DoubleConsumer compose(DoubleConsumer... consumers) {
+        DoubleConsumer action = DOUBLE_NOOP;
+        for (final DoubleConsumer consumer : consumers) {
+            if (consumer != null) {
+                action = action.andThen(consumer);
+            }
+        }
+        return action == DOUBLE_NOOP ? null : action;
+    }
+
+    /**
+     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+     * consumer. Returns {@code null} if all arguments are {@code null}.
+     *
+     * @param consumers Consumers.
+     * @return a composed consumer (or null)
+     */
+    static IntConsumer compose(IntConsumer... consumers) {
+        IntConsumer action = INT_NOOP;
+        for (final IntConsumer consumer : consumers) {
+            if (consumer != null) {
+                action = action.andThen(consumer);
+            }
+        }
+        return action == INT_NOOP ? null : action;
+    }
+
+    /**
+     * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
+     * consumer. Returns {@code null} if all arguments are {@code null}.
+     *
+     * @param consumers Consumers.
+     * @return a composed consumer (or null)
+     */
+    static LongConsumer compose(LongConsumer... consumers) {
+        LongConsumer action = LONG_NOOP;
+        for (final LongConsumer consumer : consumers) {
+            if (consumer != null) {
+                action = action.andThen(consumer);
+            }
+        }
+        return action == LONG_NOOP ? null : action;
+    }
+
+    /**
+     * Gets the statistic result using the {@code int} value.
+     * Return {@code null} is the statistic is {@code null}.
+     *
+     * @param s Statistic.
+     * @return the result or null
+     */
+    static StatisticResult getResultAsIntOrNull(StatisticResult s) {
+        if (s != null) {
+            return (IntStatisticResult) s::getAsInt;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the statistic result using the {@code long} value.
+     * Return {@code null} is the statistic is {@code null}.
+     *
+     * @param s Statistic.
+     * @return the result or null
+     */
+    static StatisticResult getResultAsLongOrNull(StatisticResult s) {
+        if (s != null) {
+            return (LongStatisticResult) s::getAsLong;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the statistic result using the {@code double} value.
+     * Return {@code null} is the statistic is {@code null}.
+     *
+     * @param s Statistic.
+     * @return the result or null
+     */
+    static StatisticResult getResultAsDoubleOrNull(StatisticResult s) {
+        if (s != null) {
+            return s::getAsDouble;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the statistic result using the {@code BigInteger} value.
+     * Return {@code null} is the statistic is {@code null}.
+     *
+     * @param s Statistic.
+     * @return the result or null
+     */
+    static StatisticResult getResultAsBigIntegerOrNull(StatisticResult s) {
+        if (s != null) {
+            return (BigIntegerStatisticResult) s::getAsBigInteger;
+        }
+        return null;
+    }
+
+    /**
+     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
+     * argument {@code b} must also be non-{@code null} so the statistics can be combined.
+     *
+     * @param <T> {@link StatisticResult} being accumulated.
+     * @param a LHS.
+     * @param b RHS.
+     * @throws IllegalArgumentException if the objects cannot be combined
+     */
+    static <T extends StatisticResult & StatisticAccumulator<T>> void checkCombineCompatible(T a, T b) {
+        if (a != null && b == null) {
+            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
+        }
+    }
+
+    /**
+     * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
+     * argument {@code b} must be run-time assignable to the same class as {@code a}
+     * so the statistics can be combined.
+     *
+     * @param a LHS.
+     * @param b RHS.
+     * @throws IllegalArgumentException if the objects cannot be combined
+     */
+    static void checkCombineAssignable(FirstMoment a, FirstMoment b) {
+        if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
+            throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
+        }
+    }
+
+    /**
+     * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
+     * right-hand side argument {@code b}.
+     *
+     * @param <T> {@link StatisticResult} being accumulated.
+     * @param a LHS.
+     * @param b RHS.
+     */
+    static <T extends StatisticResult & StatisticAccumulator<T>> void combine(T a, T b) {
+        if (a != null) {
+            a.combine(b);
+        }
+    }
+
+    /**
+     * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
+     * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
+     * to the same class as LHS.
+     *
+     * @param a LHS.
+     * @param b RHS.
+     * @see #checkCombineAssignable(FirstMoment, FirstMoment)
+     */
+    static void combineMoment(FirstMoment a, FirstMoment b) {
+        // Avoid reflection and use the simpler instanceof
+        if (a instanceof SumOfFourthDeviations) {
+            ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
+        } else if (a instanceof SumOfCubedDeviations) {
+            ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
+        } else if (a instanceof SumOfSquaredDeviations) {
+            ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
+        } else if (a != null) {
+            a.combine(b);
+        }
+    }
 }
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
index a73d855..096aa4e 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
 import java.util.function.DoubleSupplier;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -363,20 +362,6 @@ class DoubleStatisticsTest {
         return s1;
     }
 
-    @Test
-    void testNoOpConsumer() {
-        final DoubleConsumer c = DoubleStatistics.NOOP;
-        // Hit coverage
-        c.accept(0);
-        final double[] value = {0};
-        final DoubleConsumer other = x -> value[0] = x;
-        final DoubleConsumer combined = c.andThen(other);
-        Assertions.assertSame(combined, other);
-        final double y = 42;
-        combined.accept(y);
-        Assertions.assertEquals(y, value[0]);
-    }
-
     @Test
     void testOfThrows() {
         Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
@@ -574,7 +559,7 @@ class DoubleStatisticsTest {
      * @param setter Option setter.
      * @return the number of unique results
      */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
+    static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
             Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
             Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
         final T stat = arrayConstructor.apply(values);
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
similarity index 54%
copy from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
copy to commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
index a73d855..fae8307 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntStatisticsTest.java
@@ -21,18 +21,19 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
 import java.util.function.DoubleSupplier;
 import java.util.function.Function;
 import java.util.function.Supplier;
-import java.util.function.ToDoubleFunction;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,12 +43,22 @@ import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
- * Test for {@link DoubleStatistics}.
+ * Test for {@link IntStatistics}.
  *
  * <p>This class verifies that the statistics computed using the summary
  * class are an exact match to the statistics computed individually.
+ *
+ * <p>For simplicity some tests use only the {@code double} result of the statistic.
+ * The full {@link StatisticResult} interface is asserted in the test of the array or
+ * stream of input data.
+ *
+ * <p>Note: Some integer statistics are identical if computed using an array or a stream,
+ * e.g. sum, min, mean. However those that use a {@code double} implementation may vary
+ * based on input order, e.g. product, skewness. This test computes expected results
+ * for array and stream variations for all statistics and does not distinguish these two
+ * cases.
  */
-class DoubleStatisticsTest {
+class IntStatisticsTest {
     /** Empty statistic array. */
     private static final Statistic[] EMPTY_STATISTIC_ARRAY = {};
 
@@ -57,9 +68,12 @@ class DoubleStatisticsTest {
     /** The expected result for each statistic on the test data. */
     private static EnumMap<Statistic, List<ExpectedResult>> expectedResult;
 
-    /** The statistics that are co-computed. */
+    /** The statistics that are co-computed by a single statistic. */
     private static EnumMap<Statistic, EnumSet<Statistic>> coComputed;
 
+    /** The statistics that are co-computed by a combination of statistics. */
+    private static Map<EnumSet<Statistic>, EnumSet<Statistic>> coComputedAsCombination;
+
     /**
      * Container for test data.
      */
@@ -67,7 +81,7 @@ class DoubleStatisticsTest {
         /** Identifier. */
         private final int id;
         /** The sample values. */
-        private final double[][] values;
+        private final int[][] values;
         /** The number of values. */
         private final long size;
 
@@ -75,7 +89,7 @@ class DoubleStatisticsTest {
          * @param id Identifier.
          * @param values Sample values.
          */
-        TestData(int id, double[]... values) {
+        TestData(int id, int[]... values) {
             this.id = id;
             this.values = values;
             size = Arrays.stream(values).mapToLong(x -> x.length).sum();
@@ -91,7 +105,7 @@ class DoubleStatisticsTest {
         /**
          * @return the values as a stream
          */
-        Stream<double[]> stream() {
+        Stream<int[]> stream() {
             return Arrays.stream(values);
         }
 
@@ -108,15 +122,15 @@ class DoubleStatisticsTest {
      */
     private static class ExpectedResult {
         /** The expected result for a stream of values. */
-        private final double stream;
+        private final StatisticResult stream;
         /** The expected result for an array of values. */
-        private final double array;
+        private final StatisticResult array;
 
         /**
          * @param stream Stream result.
          * @param array Array result.
          */
-        ExpectedResult(double stream, double array) {
+        ExpectedResult(StatisticResult stream, StatisticResult array) {
             this.stream = stream;
             this.array = array;
         }
@@ -124,27 +138,27 @@ class DoubleStatisticsTest {
         /**
          * @return the expected result for a stream of values.
          */
-        double getStream() {
+        StatisticResult getStream() {
             return stream;
         }
 
         /**
          * @return the expected result for an array of values.
          */
-        double getArray() {
+        StatisticResult getArray() {
             return array;
         }
     }
 
     @BeforeAll
     static void setup() {
-        // Random double[] of different lengths
-        final double[][] arrays = IntStream.of(4, 5, 7)
-            .mapToObj(i -> ThreadLocalRandom.current().doubles(i, 2.25, 3.75).toArray())
-            .toArray(double[][]::new);
-        // Create test data. IDs must be created in order of the data.
+        // Random int[] of different lengths, Strictly positive so sum-of-logs is finite.
+        final int[][] arrays = IntStream.of(4, 5, 7)
+            .mapToObj(i -> ThreadLocalRandom.current().ints(i, 1, Integer.MAX_VALUE).toArray())
+            .toArray(int[][]::new);
+        // Create test data. IDs must be created in order of the data so use the list size as ID.
         testData = new ArrayList<>();
-        testData.add(new TestData(testData.size(), new double[0]));
+        testData.add(new TestData(testData.size(), new int[0]));
         testData.add(new TestData(testData.size(), arrays[0]));
         testData.add(new TestData(testData.size(), arrays[1]));
         testData.add(new TestData(testData.size(), arrays[2]));
@@ -152,61 +166,72 @@ class DoubleStatisticsTest {
         testData.add(new TestData(testData.size(), arrays[1], arrays[2]));
         // Create reference expected results
         expectedResult = new EnumMap<>(Statistic.class);
-        addExpected(Statistic.MIN, Min::create, Min::of);
-        addExpected(Statistic.MAX, Max::create, Max::of);
-        addExpected(Statistic.MEAN, Mean::create, Mean::of);
-        addExpected(Statistic.STANDARD_DEVIATION, StandardDeviation::create, StandardDeviation::of);
-        addExpected(Statistic.VARIANCE, Variance::create, Variance::of);
-        addExpected(Statistic.SKEWNESS, Skewness::create, Skewness::of);
-        addExpected(Statistic.KURTOSIS, Kurtosis::create, Kurtosis::of);
-        addExpected(Statistic.PRODUCT, Product::create, Product::of);
-        addExpected(Statistic.SUM, Sum::create, Sum::of);
-        addExpected(Statistic.SUM_OF_LOGS, SumOfLogs::create, SumOfLogs::of);
-        addExpected(Statistic.SUM_OF_SQUARES, SumOfSquares::create, SumOfSquares::of);
-        addExpected(Statistic.GEOMETRIC_MEAN, GeometricMean::create, GeometricMean::of);
+        addExpected(Statistic.MIN, IntMin::create, IntMin::of);
+        addExpected(Statistic.MAX, IntMax::create, IntMax::of);
+        addExpected(Statistic.MEAN, IntMean::create, IntMean::of);
+        addExpected(Statistic.STANDARD_DEVIATION, IntStandardDeviation::create, IntStandardDeviation::of);
+        addExpected(Statistic.VARIANCE, IntVariance::create, IntVariance::of);
+        addExpected(Statistic.SKEWNESS,
+            () -> DoubleAsIntStatistic.from(Skewness.create()),
+            x -> DoubleAsIntStatistic.from(Skewness.of(x)));
+        addExpected(Statistic.KURTOSIS,
+            () -> DoubleAsIntStatistic.from(Kurtosis.create()),
+            x -> DoubleAsIntStatistic.from(Kurtosis.of(x)));
+        addExpected(Statistic.PRODUCT,
+            () -> DoubleAsIntStatistic.from(Product.create()),
+            x -> DoubleAsIntStatistic.from(Product.of(x)));
+        addExpected(Statistic.SUM, IntSum::create, IntSum::of);
+        addExpected(Statistic.SUM_OF_LOGS,
+            () -> DoubleAsIntStatistic.from(SumOfLogs.create()),
+            x -> DoubleAsIntStatistic.from(SumOfLogs.of(x)));
+        addExpected(Statistic.SUM_OF_SQUARES, IntSumOfSquares::create, IntSumOfSquares::of);
+        addExpected(Statistic.GEOMETRIC_MEAN,
+            () -> DoubleAsIntStatistic.from(GeometricMean.create()),
+            x -> DoubleAsIntStatistic.from(GeometricMean.of(x)));
         // Create co-computed statistics
         coComputed = new EnumMap<>(Statistic.class);
         Arrays.stream(Statistic.values()).forEach(s -> coComputed.put(s, EnumSet.of(s)));
         addCoComputed(Statistic.GEOMETRIC_MEAN, Statistic.SUM_OF_LOGS);
         addCoComputed(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+        addCoComputed(Statistic.SUM, Statistic.MEAN);
+        // Create statistics computed as part of the main statistic
+        addComputedBy(Statistic.VARIANCE, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
+        addComputedBy(Statistic.STANDARD_DEVIATION, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
         // Cascade moments up
-        EnumSet<Statistic> m = coComputed.get(Statistic.MEAN);
-        coComputed.get(Statistic.STANDARD_DEVIATION).addAll(m);
-        coComputed.get(Statistic.VARIANCE).addAll(m);
-        m = coComputed.get(Statistic.VARIANCE);
-        coComputed.get(Statistic.SKEWNESS).addAll(m);
-        m = coComputed.get(Statistic.SKEWNESS);
-        coComputed.get(Statistic.KURTOSIS).addAll(m);
+        coComputed.get(Statistic.KURTOSIS).add(Statistic.SKEWNESS);
+        // Combinations
+        coComputedAsCombination = new HashMap<>();
+        final EnumSet<Statistic> s = EnumSet.of(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+        coComputedAsCombination.put(EnumSet.of(Statistic.MEAN, Statistic.SUM_OF_SQUARES), s);
+        coComputedAsCombination.put(EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES), s);
     }
 
     /**
      * Adds the expected result for the specified {@code statistic}.
      *
-     * @param <T> {@link DoubleStatistic} being computed.
+     * @param <T> {@link IntStatistic} being computed.
      * @param statistic Statistic.
      * @param constructor Constructor for an empty object.
      * @param arrayConstructor Constructor using an array of values.
      */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
-            Supplier<T> constructor, Function<double[], T> arrayConstructor) {
+    private static <T extends IntStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
+            Supplier<T> constructor, Function<int[], T> arrayConstructor) {
         final List<ExpectedResult> results = new ArrayList<>();
         for (final TestData d : testData) {
             // Stream values
-            final double e1 = d.stream()
+            final StatisticResult e1 = d.stream()
                 .map(values -> Statistics.add(constructor.get(), values))
                 .reduce(StatisticAccumulator::combine)
-                .orElseThrow(IllegalStateException::new)
-                .getAsDouble();
+                .orElseThrow(IllegalStateException::new);
             // Create from array
-            final double e2 = d.stream()
+            final StatisticResult e2 = d.stream()
                 .map(arrayConstructor)
                 .reduce(StatisticAccumulator::combine)
-                .orElseThrow(IllegalStateException::new)
-                .getAsDouble();
+                .orElseThrow(IllegalStateException::new);
             // Check that there is a finite value to compute during testing
             if (d.size() != 0) {
-                assertFinite(e1, statistic);
-                assertFinite(e2, statistic);
+                assertFinite(e1.getAsDouble(), statistic);
+                assertFinite(e2.getAsDouble(), statistic);
             }
             results.add(new ExpectedResult(e1, e2));
         }
@@ -226,6 +251,17 @@ class DoubleStatisticsTest {
         coComputed.get(s2).add(s1);
     }
 
+    /**
+     * Adds the child statistics that are computed by the parent statistic to the co-computed mapping.
+     * The statistics are a one-way relationship (a computes b but b does not compute a).
+     *
+     * @param parent First statistic.
+     * @param children Computed statistics.
+     */
+    private static void addComputedBy(Statistic parent, Statistic... children) {
+        coComputed.get(parent).addAll(EnumSet.of(parent, children));
+    }
+
     @AfterAll
     static void teardown() {
         // Free memory
@@ -251,7 +287,7 @@ class DoubleStatisticsTest {
     }
 
     /**
-     * Test the {@link DoubleStatistics} when all data is passed as a stream of single values.
+     * Test the {@link IntStatistics} when all data is passed as a stream of single values.
      */
     @ParameterizedTest
     @MethodSource(value = {"streamTestData"})
@@ -262,21 +298,29 @@ class DoubleStatisticsTest {
     }
 
     /**
-     * Test the {@link DoubleStatistics} when data is passed as a {@code double[]} of values.
+     * Test the {@link IntStatistics} when data is passed as a {@code int[]} of values.
      */
     @ParameterizedTest
     @MethodSource(value = {"streamTestData"})
     void testArray(EnumSet<Statistic> stats, TestData data) {
         // Test creation from a builder
-        final DoubleStatistics.Builder builder = DoubleStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
+        final IntStatistics.Builder builder = IntStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
         assertStatistics(stats, data, builder::build, ExpectedResult::getArray);
     }
 
+    /**
+     * Assert the computed statistics match the expected result.
+     *
+     * @param stats Statistics that are computed.
+     * @param data Test data.
+     * @param constructor Constructor to create the {@link IntStatistics}.
+     * @param expected Function to obtain the expected result.
+     */
     private static void assertStatistics(EnumSet<Statistic> stats, TestData data,
-            Function<double[], DoubleStatistics> constructor,
-            ToDoubleFunction<ExpectedResult> expected) {
+            Function<int[], IntStatistics> constructor,
+            Function<ExpectedResult, StatisticResult> expected) {
         final Statistic[] statsArray = stats.toArray(EMPTY_STATISTIC_ARRAY);
-        final DoubleStatistics statistics = data.stream()
+        final IntStatistics statistics = data.stream()
             .map(constructor)
             .reduce((a, b) -> combine(statsArray, a, b))
             .orElseThrow(IllegalStateException::new);
@@ -284,56 +328,88 @@ class DoubleStatisticsTest {
         Assertions.assertEquals(data.size(), statistics.getCount(), "Count");
         final EnumSet<Statistic> computed = EnumSet.copyOf(stats);
         stats.forEach(s -> computed.addAll(coComputed.get(s)));
+        // Currently we only have to support pairs here as the test only uses paired stats.
+        // Add an assertion to ensure this is fixed in future if required (e.g. using an
+        // enumeration of all possible combinations of the input stats as keys into
+        // coComputedAsCombination).
+        Assertions.assertFalse(stats.size() > 2, "Combinations other than pairs are not supported");
+        computed.addAll(coComputedAsCombination.getOrDefault(stats, EnumSet.noneOf(Statistic.class)));
 
         // Test if the statistics are correctly identified as supported
-        EnumSet.allOf(Statistic.class).forEach(s ->
-            Assertions.assertEquals(computed.contains(s), statistics.isSupported(s),
-                () -> stats + " isSupported -> " + s.toString()));
-
-        // Test the values
-        computed.forEach(s ->
-            Assertions.assertEquals(expected.applyAsDouble(expectedResult.get(s).get(id)), statistics.get(s),
-                () -> stats + " value -> " + s.toString()));
+        EnumSet.allOf(Statistic.class).forEach(s -> {
+            final boolean isSupported = computed.contains(s);
+            Assertions.assertEquals(isSupported, statistics.isSupported(s),
+                () -> stats + " isSupported -> " + s.toString());
+            if (isSupported) {
+                final StatisticResult result = expected.apply(expectedResult.get(s).get(id));
+                // Test individual values
+                TestHelper.assertDoubleEquals(result, () -> statistics.getAsDouble(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getAsDouble -> " + s.toString());
+                TestHelper.assertIntEquals(result, () -> statistics.getAsInt(s),
+                    () -> stats + " getAsInt -> " + s.toString());
+                TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                TestHelper.assertBigIntegerEquals(result, () -> statistics.getAsBigInteger(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getAsBigInteger -> " + s.toString());
+                // Test the values from the result
+                TestHelper.assertEquals(result, statistics.getResult(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getResult -> " + s.toString());
+            } else {
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsDouble(s),
+                    () -> stats + " getAsDouble -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsInt(s),
+                    () -> stats + " getAsInt -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsBigInteger(s),
+                    () -> stats + " getAsBigInteger -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getResult(s),
+                    () -> stats + " getResult -> " + s.toString());
+            }
+        });
     }
 
     /**
      * Add all the {@code values} to an aggregator of the {@code statistics}.
      *
-     * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
-     * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
+     * <p>This method verifies that the {@link IntStatistics#getAsDouble(Statistic)} and
+     * {@link IntStatistics#getResult(Statistic)} methods return the same
      * result as values are added.
      *
-     * @param statistic Statistics.
+     * @param statistics Statistics.
      * @param values Values.
      * @return the statistics
      */
-    private static DoubleStatistics acceptAll(Statistic[] statistics, double[] values) {
-        final DoubleStatistics stats = DoubleStatistics.of(statistics);
-        final DoubleSupplier[] f = getSuppliers(statistics, stats);
-        for (final double x : values) {
+    private static IntStatistics acceptAll(Statistic[] statistics, int[] values) {
+        final IntStatistics stats = IntStatistics.of(statistics);
+        final StatisticResult[] f = getResults(statistics, stats);
+        for (final int x : values) {
             stats.accept(x);
             for (int i = 0; i < statistics.length; i++) {
                 final Statistic s = statistics[i];
-                Assertions.assertEquals(stats.get(s), f[i].getAsDouble(),
-                    () -> "Supplier(" + s + ") after value " + x);
+                Assertions.assertEquals(stats.getAsDouble(s), f[i].getAsDouble(),
+                    () -> "StatisticResult(" + s + ") after value " + x);
             }
         }
         return stats;
     }
 
     /**
-     * Gets the suppliers for the {@code statistics}.
+     * Gets the results for the {@code statistics}.
      *
      * @param statistics Statistics to compute.
      * @param stats Statistic aggregator.
-     * @return the suppliers
+     * @return the results
      */
-    private static DoubleSupplier[] getSuppliers(Statistic[] statistics, final DoubleStatistics stats) {
-        final DoubleSupplier[] f = new DoubleSupplier[statistics.length];
+    private static StatisticResult[] getResults(Statistic[] statistics, final IntStatistics stats) {
+        final StatisticResult[] f = new StatisticResult[statistics.length];
         for (int i = 0; i < statistics.length; i++) {
-            final DoubleSupplier supplier = stats.getSupplier(statistics[i]);
-            Assertions.assertFalse(supplier instanceof DoubleStatistic,
-                () -> "DoubleStatistic instance: " + supplier.getClass().getSimpleName());
+            final StatisticResult supplier = stats.getResult(statistics[i]);
+            Assertions.assertFalse(supplier instanceof IntStatistic,
+                () -> "IntStatistic instance: " + supplier.getClass().getSimpleName());
             f[i] = supplier;
         }
         return f;
@@ -342,153 +418,115 @@ class DoubleStatisticsTest {
     /**
      * Combine the two statistic aggregators.
      *
-     * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
-     * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
-     * result after the {@link DoubleStatistics#combine(DoubleStatistics)}.
+     * <p>This method verifies that the {@link IntStatistics#getAsDouble(Statistic)} and
+     * {@link IntStatistics#getResult(Statistic)} methods return the same
+     * result after the {@link IntStatistics#combine(IntStatistics)}.
      *
      * @param statistics Statistics to compute.
      * @param s1 Statistic aggregator.
      * @param s2 Statistic aggregator.
-     * @return the double statistics
+     * @return the combined IntStatistics
      */
-    private static DoubleStatistics combine(Statistic[] statistics,
-        DoubleStatistics s1, DoubleStatistics s2) {
-        final DoubleSupplier[] f = getSuppliers(statistics, s1);
+    private static IntStatistics combine(Statistic[] statistics,
+        IntStatistics s1, IntStatistics s2) {
+        final StatisticResult[] f = getResults(statistics, s1);
         s1.combine(s2);
         for (int i = 0; i < statistics.length; i++) {
             final Statistic s = statistics[i];
-            Assertions.assertEquals(s1.get(s), f[i].getAsDouble(),
-                () -> "Supplier(" + s + ") after combine");
+            Assertions.assertEquals(s1.getAsDouble(s), f[i].getAsDouble(),
+                () -> "StatisticResult(" + s + ") after combine");
         }
         return s1;
     }
 
-    @Test
-    void testNoOpConsumer() {
-        final DoubleConsumer c = DoubleStatistics.NOOP;
-        // Hit coverage
-        c.accept(0);
-        final double[] value = {0};
-        final DoubleConsumer other = x -> value[0] = x;
-        final DoubleConsumer combined = c.andThen(other);
-        Assertions.assertSame(combined, other);
-        final double y = 42;
-        combined.accept(y);
-        Assertions.assertEquals(y, value[0]);
-    }
-
     @Test
     void testOfThrows() {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(EMPTY_STATISTIC_ARRAY));
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(new Statistic[1]));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of());
+        Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of(EMPTY_STATISTIC_ARRAY));
+        Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(new Statistic[1]));
     }
 
     @Test
     void testOfSetThrows() {
         final EnumSet<Statistic> s1 = EnumSet.noneOf(Statistic.class);
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(s1));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.of(s1));
         final EnumSet<Statistic> s2 = null;
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s2));
+        Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(s2));
         final EnumSet<Statistic> s3 = EnumSet.of(Statistic.MIN);
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s3, null));
+        final int[] nullValues = null;
+        Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.of(s3, nullValues));
     }
 
     @Test
     void testBuilderThrows() {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder());
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder(EMPTY_STATISTIC_ARRAY));
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.builder(new Statistic[1]));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.builder());
+        Assertions.assertThrows(IllegalArgumentException.class, () -> IntStatistics.builder(EMPTY_STATISTIC_ARRAY));
+        Assertions.assertThrows(NullPointerException.class, () -> IntStatistics.builder(new Statistic[1]));
     }
 
     @Test
     void testIsSupportedWithNull() {
-        DoubleStatistics s = DoubleStatistics.of(Statistic.MIN);
+        IntStatistics s = IntStatistics.of(Statistic.MIN);
         Assertions.assertThrows(NullPointerException.class, () -> s.isSupported(null));
     }
 
-    @ParameterizedTest
-    @MethodSource
-    void testNotSupported(Statistic stat) {
-        DoubleStatistics statistics = DoubleStatistics.of(stat);
-        for (final Statistic s : Statistic.values()) {
-            Assertions.assertEquals(s == stat, statistics.isSupported(s),
-                () -> stat + " isSupported -> " + s.toString());
-            if (s == stat) {
-                Assertions.assertDoesNotThrow(() -> statistics.get(s),
-                    () -> stat + " get -> " + s.toString());
-                Assertions.assertNotNull(statistics.getSupplier(s),
-                    () -> stat + " getSupplier -> " + s.toString());
-            } else {
-                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.get(s),
-                    () -> stat + " get -> " + s.toString());
-                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getSupplier(s),
-                    () -> stat + " getSupplier -> " + s.toString());
-            }
-        }
-    }
-
-    static Statistic[] testNotSupported() {
-        return new Statistic[] {Statistic.MIN, Statistic.PRODUCT};
-    }
-
     @ParameterizedTest
     @MethodSource
     void testIncompatibleCombineThrows(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
-        final double[] v1 = {1, 2, 3.5, 6};
-        final double[] v2 = {3, 4, 5};
-        DoubleStatistics statistics = DoubleStatistics.of(stat1, v1);
-        DoubleStatistics other = DoubleStatistics.of(stat2, v2);
+        final int[] v1 = {1, 2, 4, 6};
+        final int[] v2 = {3, 4, 5};
+        IntStatistics statistics = IntStatistics.of(stat1, v1);
+        IntStatistics other = IntStatistics.of(stat2, v2);
         // Store values
-        final double[] values = stat1.stream().mapToDouble(statistics::get).toArray();
+        final double[] values = stat1.stream().mapToDouble(statistics::getAsDouble).toArray();
         Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.combine(other),
             () -> stat1 + " " + stat2);
         // Values should be unchanged
         final int[] i = {0};
         stat1.stream().forEach(
-            s -> Assertions.assertEquals(values[i[0]++], statistics.get(s), () -> s + " changed"));
+            s -> Assertions.assertEquals(values[i[0]++], statistics.getAsDouble(s), () -> s + " changed"));
     }
 
     static Stream<Arguments> testIncompatibleCombineThrows() {
         return Stream.of(
             Arguments.of(EnumSet.of(Statistic.MIN), EnumSet.of(Statistic.PRODUCT)),
             Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MIN)),
-            // Note: MEAN is compatible with VARIANCE. The combine is not symmetric.
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MEAN))
+            // Note: SKEWNESS is compatible with KURTOSIS. The combine is not symmetric.
+            Arguments.of(EnumSet.of(Statistic.KURTOSIS), EnumSet.of(Statistic.SKEWNESS))
         );
     }
 
     @ParameterizedTest
     @MethodSource
     void testCompatibleCombine(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
-        final double[] v1 = {1, 2, 3.5, 6};
-        final double[] v2 = {3, 4, 5};
-        final DoubleStatistics statistics1 = DoubleStatistics.of(stat1, v1);
-        final DoubleStatistics statistics2 = DoubleStatistics.of(stat1, v1);
+        final int[] v1 = {1, 2, 4, 6};
+        final int[] v2 = {3, 4, 5};
+        final IntStatistics statistics1 = IntStatistics.of(stat1, v1);
+        final IntStatistics statistics2 = IntStatistics.of(stat1, v1);
         // Note: other1 is intentionally using the same flags as statistics1
-        final DoubleStatistics other1 = DoubleStatistics.of(stat1, v2);
-        final DoubleStatistics other2 = DoubleStatistics.of(stat2, v2);
+        final IntStatistics other1 = IntStatistics.of(stat1, v2);
+        final IntStatistics other2 = IntStatistics.of(stat2, v2);
         // This should work
         statistics1.combine(other1);
         // This should be compatible
         statistics2.combine(other2);
         // The stats should be the same
         for (final Statistic s : stat1) {
-            final double expected = statistics1.get(s);
+            final double expected = statistics1.getAsDouble(s);
             assertFinite(expected, s);
-            Assertions.assertEquals(expected, statistics2.get(s), () -> s.toString());
+            Assertions.assertEquals(expected, statistics2.getAsDouble(s), () -> s.toString());
         }
     }
 
     static Stream<Arguments> testCompatibleCombine() {
         return Stream.of(
             Arguments.of(EnumSet.of(Statistic.MEAN), EnumSet.of(Statistic.VARIANCE)),
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SKEWNESS)),
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.KURTOSIS)),
+            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES)),
+            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.STANDARD_DEVIATION)),
             Arguments.of(EnumSet.of(Statistic.GEOMETRIC_MEAN), EnumSet.of(Statistic.SUM_OF_LOGS)),
             // Compatible combinations
             Arguments.of(EnumSet.of(Statistic.VARIANCE, Statistic.MIN, Statistic.SKEWNESS),
-                         EnumSet.of(Statistic.KURTOSIS, Statistic.MEAN, Statistic.MIN))
+                         EnumSet.of(Statistic.KURTOSIS, Statistic.SUM_OF_SQUARES, Statistic.MEAN, Statistic.MIN))
         );
     }
 
@@ -498,8 +536,8 @@ class DoubleStatisticsTest {
 
     @ParameterizedTest
     @MethodSource
-    void testBiased(Statistic stat, double[] values, boolean[] options, double[] results) {
-        final DoubleStatistics statistics1 = DoubleStatistics.builder(stat).build(values);
+    void testBiased(Statistic stat, int[] values, boolean[] options, double[] results) {
+        final IntStatistics statistics1 = IntStatistics.builder(stat).build(values);
 
         StatisticsConfiguration c = StatisticsConfiguration.withDefaults();
         DoubleSupplier s = null;
@@ -510,32 +548,32 @@ class DoubleStatisticsTest {
             c = c.withBiased(value);
             Assertions.assertSame(statistics1, statistics1.setConfiguration(c));
 
-            Assertions.assertEquals(results[i], statistics1.get(stat),
-                () -> options[i] + " get: " + BaseDoubleStatisticTest.format(values));
-            final DoubleSupplier s1 = statistics1.getSupplier(stat);
+            Assertions.assertEquals(results[i], statistics1.getAsDouble(stat),
+                () -> options[i] + " get: " + BaseIntStatisticTest.format(values));
+            final DoubleSupplier s1 = statistics1.getResult(stat);
             Assertions.assertEquals(results[i], s1.getAsDouble(),
-                () -> options[i] + " supplier: " + BaseDoubleStatisticTest.format(values));
+                () -> options[i] + " supplier: " + BaseIntStatisticTest.format(values));
 
             // Config change does not propagate to previous supplier
             if (s != null) {
                 final int j = (i - 1 + options.length) % options.length;
                 Assertions.assertEquals(results[j], s.getAsDouble(),
-                    () -> options[j] + " previous supplier: " + BaseDoubleStatisticTest.format(values));
+                    () -> options[j] + " previous supplier: " + BaseIntStatisticTest.format(values));
             }
             s = s1;
 
             // Set through the builder
-            final DoubleStatistics statistics2 = DoubleStatistics.builder(stat)
+            final IntStatistics statistics2 = IntStatistics.builder(stat)
                 .setConfiguration(c).build(values);
-            Assertions.assertEquals(results[i], statistics2.get(stat),
-                () -> options[i] + " get via builder: " + BaseDoubleStatisticTest.format(values));
+            Assertions.assertEquals(results[i], statistics2.getAsDouble(stat),
+                () -> options[i] + " get via builder: " + BaseIntStatisticTest.format(values));
         }
     }
 
     static Stream<Arguments> testBiased() {
         final Stream.Builder<Arguments> builder = Stream.builder();
         // Data must generate different results for different options
-        final double[][] data = {
+        final int[][] data = {
             {1},
             {1, 2},
             {1, 2, 4},
@@ -545,9 +583,9 @@ class DoubleStatisticsTest {
         final Statistic[] stats = {Statistic.STANDARD_DEVIATION, Statistic.VARIANCE,
             Statistic.SKEWNESS, Statistic.KURTOSIS};
         final int[] diff = new int[stats.length];
-        for (final double[] d : data) {
-            diff[0] += addBooleanOptionCase(builder, d, stats[0], StandardDeviation::of, StandardDeviation::setBiased);
-            diff[1] += addBooleanOptionCase(builder, d, stats[1], Variance::of, Variance::setBiased);
+        for (final int[] d : data) {
+            diff[0] += addBooleanOptionCase(builder, d, stats[0], IntStandardDeviation::of, IntStandardDeviation::setBiased);
+            diff[1] += addBooleanOptionCase(builder, d, stats[1], IntVariance::of, IntVariance::setBiased);
             diff[2] += addBooleanOptionCase(builder, d, stats[2], Skewness::of, Skewness::setBiased);
             diff[3] += addBooleanOptionCase(builder, d, stats[3], Kurtosis::of, Kurtosis::setBiased);
         }
@@ -566,7 +604,7 @@ class DoubleStatisticsTest {
      * <p>This method returns the number of unique results. It does not enforce that the
      * results are different.
      *
-     * @param <T> {@link DoubleStatistic} being computed.
+     * @param <T> {@link StatisticResult} being computed.
      * @param builder Argument builder.
      * @param values Values.
      * @param statistic Statistic.
@@ -574,9 +612,9 @@ class DoubleStatisticsTest {
      * @param setter Option setter.
      * @return the number of unique results
      */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
-            Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
-            Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
+    private static <T extends StatisticResult & StatisticAccumulator<T>> int addBooleanOptionCase(
+            Stream.Builder<Arguments> builder, int[] values, Statistic statistic,
+            Function<int[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
         final T stat = arrayConstructor.apply(values);
         final boolean[] options = {true, false};
         final double[] results = new double[2];
@@ -589,4 +627,12 @@ class DoubleStatisticsTest {
         builder.accept(Arguments.of(statistic, values, options, results));
         return all.size();
     }
+
+    @Test
+    void testMoments() {
+        // Hits coverage within the builder for when kurtosis is ordered before skewness
+        final IntStatistics s = IntStatistics.of(Statistic.KURTOSIS, Statistic.SKEWNESS);
+        Assertions.assertTrue(s.isSupported(Statistic.KURTOSIS));
+        Assertions.assertTrue(s.isSupported(Statistic.SKEWNESS));
+    }
 }
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
similarity index 54%
copy from commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
copy to commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
index a73d855..4ea1ba6 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/DoubleStatisticsTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongStatisticsTest.java
@@ -21,18 +21,19 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.BiConsumer;
-import java.util.function.DoubleConsumer;
 import java.util.function.DoubleSupplier;
 import java.util.function.Function;
 import java.util.function.Supplier;
-import java.util.function.ToDoubleFunction;
-import java.util.stream.IntStream;
+import java.util.stream.LongStream;
 import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,12 +43,22 @@ import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
- * Test for {@link DoubleStatistics}.
+ * Test for {@link LongStatistics}.
  *
  * <p>This class verifies that the statistics computed using the summary
  * class are an exact match to the statistics computed individually.
+ *
+ * <p>For simplicity some tests use only the {@code double} result of the statistic.
+ * The full {@link StatisticResult} interface is asserted in the test of the array or
+ * stream of input data.
+ *
+ * <p>Note: Some integer statistics are identical if computed using an array or a stream,
+ * e.g. sum, min, mean. However those that use a {@code double} implementation may vary
+ * based on input order, e.g. product, skewness. This test computes expected results
+ * for array and stream variations for all statistics and does not distinguish these two
+ * cases.
  */
-class DoubleStatisticsTest {
+class LongStatisticsTest {
     /** Empty statistic array. */
     private static final Statistic[] EMPTY_STATISTIC_ARRAY = {};
 
@@ -57,9 +68,12 @@ class DoubleStatisticsTest {
     /** The expected result for each statistic on the test data. */
     private static EnumMap<Statistic, List<ExpectedResult>> expectedResult;
 
-    /** The statistics that are co-computed. */
+    /** The statistics that are co-computed by a single statistic. */
     private static EnumMap<Statistic, EnumSet<Statistic>> coComputed;
 
+    /** The statistics that are co-computed by a combination of statistics. */
+    private static Map<EnumSet<Statistic>, EnumSet<Statistic>> coComputedAsCombination;
+
     /**
      * Container for test data.
      */
@@ -67,7 +81,7 @@ class DoubleStatisticsTest {
         /** Identifier. */
         private final int id;
         /** The sample values. */
-        private final double[][] values;
+        private final long[][] values;
         /** The number of values. */
         private final long size;
 
@@ -75,7 +89,7 @@ class DoubleStatisticsTest {
          * @param id Identifier.
          * @param values Sample values.
          */
-        TestData(int id, double[]... values) {
+        TestData(int id, long[]... values) {
             this.id = id;
             this.values = values;
             size = Arrays.stream(values).mapToLong(x -> x.length).sum();
@@ -91,7 +105,7 @@ class DoubleStatisticsTest {
         /**
          * @return the values as a stream
          */
-        Stream<double[]> stream() {
+        Stream<long[]> stream() {
             return Arrays.stream(values);
         }
 
@@ -108,15 +122,15 @@ class DoubleStatisticsTest {
      */
     private static class ExpectedResult {
         /** The expected result for a stream of values. */
-        private final double stream;
+        private final StatisticResult stream;
         /** The expected result for an array of values. */
-        private final double array;
+        private final StatisticResult array;
 
         /**
          * @param stream Stream result.
          * @param array Array result.
          */
-        ExpectedResult(double stream, double array) {
+        ExpectedResult(StatisticResult stream, StatisticResult array) {
             this.stream = stream;
             this.array = array;
         }
@@ -124,27 +138,27 @@ class DoubleStatisticsTest {
         /**
          * @return the expected result for a stream of values.
          */
-        double getStream() {
+        StatisticResult getStream() {
             return stream;
         }
 
         /**
          * @return the expected result for an array of values.
          */
-        double getArray() {
+        StatisticResult getArray() {
             return array;
         }
     }
 
     @BeforeAll
     static void setup() {
-        // Random double[] of different lengths
-        final double[][] arrays = IntStream.of(4, 5, 7)
-            .mapToObj(i -> ThreadLocalRandom.current().doubles(i, 2.25, 3.75).toArray())
-            .toArray(double[][]::new);
-        // Create test data. IDs must be created in order of the data.
+        // Random long[] of different lengths. Strictly positive so sum-of-logs is finite.
+        final long[][] arrays = LongStream.of(4, 5, 7)
+            .mapToObj(i -> ThreadLocalRandom.current().longs(i, 1, Long.MAX_VALUE).toArray())
+            .toArray(long[][]::new);
+        // Create test data. IDs must be created in order of the data so use the list size as ID.
         testData = new ArrayList<>();
-        testData.add(new TestData(testData.size(), new double[0]));
+        testData.add(new TestData(testData.size(), new long[0]));
         testData.add(new TestData(testData.size(), arrays[0]));
         testData.add(new TestData(testData.size(), arrays[1]));
         testData.add(new TestData(testData.size(), arrays[2]));
@@ -152,61 +166,72 @@ class DoubleStatisticsTest {
         testData.add(new TestData(testData.size(), arrays[1], arrays[2]));
         // Create reference expected results
         expectedResult = new EnumMap<>(Statistic.class);
-        addExpected(Statistic.MIN, Min::create, Min::of);
-        addExpected(Statistic.MAX, Max::create, Max::of);
-        addExpected(Statistic.MEAN, Mean::create, Mean::of);
-        addExpected(Statistic.STANDARD_DEVIATION, StandardDeviation::create, StandardDeviation::of);
-        addExpected(Statistic.VARIANCE, Variance::create, Variance::of);
-        addExpected(Statistic.SKEWNESS, Skewness::create, Skewness::of);
-        addExpected(Statistic.KURTOSIS, Kurtosis::create, Kurtosis::of);
-        addExpected(Statistic.PRODUCT, Product::create, Product::of);
-        addExpected(Statistic.SUM, Sum::create, Sum::of);
-        addExpected(Statistic.SUM_OF_LOGS, SumOfLogs::create, SumOfLogs::of);
-        addExpected(Statistic.SUM_OF_SQUARES, SumOfSquares::create, SumOfSquares::of);
-        addExpected(Statistic.GEOMETRIC_MEAN, GeometricMean::create, GeometricMean::of);
+        addExpected(Statistic.MIN, LongMin::create, LongMin::of);
+        addExpected(Statistic.MAX, LongMax::create, LongMax::of);
+        addExpected(Statistic.MEAN, LongMean::create, LongMean::of);
+        addExpected(Statistic.STANDARD_DEVIATION, LongStandardDeviation::create, LongStandardDeviation::of);
+        addExpected(Statistic.VARIANCE, LongVariance::create, LongVariance::of);
+        addExpected(Statistic.SKEWNESS,
+            () -> DoubleAsLongStatistic.from(Skewness.create()),
+            x -> DoubleAsLongStatistic.from(Skewness.of(x)));
+        addExpected(Statistic.KURTOSIS,
+            () -> DoubleAsLongStatistic.from(Kurtosis.create()),
+            x -> DoubleAsLongStatistic.from(Kurtosis.of(x)));
+        addExpected(Statistic.PRODUCT,
+            () -> DoubleAsLongStatistic.from(Product.create()),
+            x -> DoubleAsLongStatistic.from(Product.of(x)));
+        addExpected(Statistic.SUM, LongSum::create, LongSum::of);
+        addExpected(Statistic.SUM_OF_LOGS,
+            () -> DoubleAsLongStatistic.from(SumOfLogs.create()),
+            x -> DoubleAsLongStatistic.from(SumOfLogs.of(x)));
+        addExpected(Statistic.SUM_OF_SQUARES, LongSumOfSquares::create, LongSumOfSquares::of);
+        addExpected(Statistic.GEOMETRIC_MEAN,
+            () -> DoubleAsLongStatistic.from(GeometricMean.create()),
+            x -> DoubleAsLongStatistic.from(GeometricMean.of(x)));
         // Create co-computed statistics
         coComputed = new EnumMap<>(Statistic.class);
         Arrays.stream(Statistic.values()).forEach(s -> coComputed.put(s, EnumSet.of(s)));
         addCoComputed(Statistic.GEOMETRIC_MEAN, Statistic.SUM_OF_LOGS);
         addCoComputed(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+        addCoComputed(Statistic.SUM, Statistic.MEAN);
+        // Create statistics computed as part of the main statistic
+        addComputedBy(Statistic.VARIANCE, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
+        addComputedBy(Statistic.STANDARD_DEVIATION, Statistic.SUM, Statistic.MEAN, Statistic.SUM_OF_SQUARES);
         // Cascade moments up
-        EnumSet<Statistic> m = coComputed.get(Statistic.MEAN);
-        coComputed.get(Statistic.STANDARD_DEVIATION).addAll(m);
-        coComputed.get(Statistic.VARIANCE).addAll(m);
-        m = coComputed.get(Statistic.VARIANCE);
-        coComputed.get(Statistic.SKEWNESS).addAll(m);
-        m = coComputed.get(Statistic.SKEWNESS);
-        coComputed.get(Statistic.KURTOSIS).addAll(m);
+        coComputed.get(Statistic.KURTOSIS).add(Statistic.SKEWNESS);
+        // Combinations
+        coComputedAsCombination = new HashMap<>();
+        final EnumSet<Statistic> s = EnumSet.of(Statistic.VARIANCE, Statistic.STANDARD_DEVIATION);
+        coComputedAsCombination.put(EnumSet.of(Statistic.MEAN, Statistic.SUM_OF_SQUARES), s);
+        coComputedAsCombination.put(EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES), s);
     }
 
     /**
      * Adds the expected result for the specified {@code statistic}.
      *
-     * @param <T> {@link DoubleStatistic} being computed.
+     * @param <T> {@link LongStatistic} being computed.
      * @param statistic Statistic.
      * @param constructor Constructor for an empty object.
      * @param arrayConstructor Constructor using an array of values.
      */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
-            Supplier<T> constructor, Function<double[], T> arrayConstructor) {
+    private static <T extends LongStatistic & StatisticAccumulator<T>> void addExpected(Statistic statistic,
+            Supplier<T> constructor, Function<long[], T> arrayConstructor) {
         final List<ExpectedResult> results = new ArrayList<>();
         for (final TestData d : testData) {
             // Stream values
-            final double e1 = d.stream()
+            final StatisticResult e1 = d.stream()
                 .map(values -> Statistics.add(constructor.get(), values))
                 .reduce(StatisticAccumulator::combine)
-                .orElseThrow(IllegalStateException::new)
-                .getAsDouble();
+                .orElseThrow(IllegalStateException::new);
             // Create from array
-            final double e2 = d.stream()
+            final StatisticResult e2 = d.stream()
                 .map(arrayConstructor)
                 .reduce(StatisticAccumulator::combine)
-                .orElseThrow(IllegalStateException::new)
-                .getAsDouble();
+                .orElseThrow(IllegalStateException::new);
             // Check that there is a finite value to compute during testing
             if (d.size() != 0) {
-                assertFinite(e1, statistic);
-                assertFinite(e2, statistic);
+                assertFinite(e1.getAsDouble(), statistic);
+                assertFinite(e2.getAsDouble(), statistic);
             }
             results.add(new ExpectedResult(e1, e2));
         }
@@ -226,6 +251,17 @@ class DoubleStatisticsTest {
         coComputed.get(s2).add(s1);
     }
 
+    /**
+     * Adds the child statistics that are computed by the parent statistic to the co-computed mapping.
+     * The statistics are a one-way relationship (a computes b but b does not compute a).
+     *
+     * @param parent First statistic.
+     * @param children Computed statistics.
+     */
+    private static void addComputedBy(Statistic parent, Statistic... children) {
+        coComputed.get(parent).addAll(EnumSet.of(parent, children));
+    }
+
     @AfterAll
     static void teardown() {
         // Free memory
@@ -251,7 +287,7 @@ class DoubleStatisticsTest {
     }
 
     /**
-     * Test the {@link DoubleStatistics} when all data is passed as a stream of single values.
+     * Test the {@link LongStatistics} when all data is passed as a stream of single values.
      */
     @ParameterizedTest
     @MethodSource(value = {"streamTestData"})
@@ -262,21 +298,29 @@ class DoubleStatisticsTest {
     }
 
     /**
-     * Test the {@link DoubleStatistics} when data is passed as a {@code double[]} of values.
+     * Test the {@link LongStatistics} when data is passed as a {@code long[]} of values.
      */
     @ParameterizedTest
     @MethodSource(value = {"streamTestData"})
     void testArray(EnumSet<Statistic> stats, TestData data) {
         // Test creation from a builder
-        final DoubleStatistics.Builder builder = DoubleStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
+        final LongStatistics.Builder builder = LongStatistics.builder(stats.toArray(EMPTY_STATISTIC_ARRAY));
         assertStatistics(stats, data, builder::build, ExpectedResult::getArray);
     }
 
+    /**
+     * Assert the computed statistics match the expected result.
+     *
+     * @param stats Statistics that are computed.
+     * @param data Test data.
+     * @param constructor Constructor to create the {@link LongStatistics}.
+     * @param expected Function to obtain the expected result.
+     */
     private static void assertStatistics(EnumSet<Statistic> stats, TestData data,
-            Function<double[], DoubleStatistics> constructor,
-            ToDoubleFunction<ExpectedResult> expected) {
+            Function<long[], LongStatistics> constructor,
+            Function<ExpectedResult, StatisticResult> expected) {
         final Statistic[] statsArray = stats.toArray(EMPTY_STATISTIC_ARRAY);
-        final DoubleStatistics statistics = data.stream()
+        final LongStatistics statistics = data.stream()
             .map(constructor)
             .reduce((a, b) -> combine(statsArray, a, b))
             .orElseThrow(IllegalStateException::new);
@@ -284,56 +328,88 @@ class DoubleStatisticsTest {
         Assertions.assertEquals(data.size(), statistics.getCount(), "Count");
         final EnumSet<Statistic> computed = EnumSet.copyOf(stats);
         stats.forEach(s -> computed.addAll(coComputed.get(s)));
+        // Currently we only have to support pairs here as the test only uses paired stats.
+        // Add an assertion to ensure this is fixed in future if required (e.g. using an
+        // enumeration of all possible combinations of the input stats as keys into
+        // coComputedAsCombination).
+        Assertions.assertFalse(stats.size() > 2, "Combinations other than pairs are not supported");
+        computed.addAll(coComputedAsCombination.getOrDefault(stats, EnumSet.noneOf(Statistic.class)));
 
         // Test if the statistics are correctly identified as supported
-        EnumSet.allOf(Statistic.class).forEach(s ->
-            Assertions.assertEquals(computed.contains(s), statistics.isSupported(s),
-                () -> stats + " isSupported -> " + s.toString()));
-
-        // Test the values
-        computed.forEach(s ->
-            Assertions.assertEquals(expected.applyAsDouble(expectedResult.get(s).get(id)), statistics.get(s),
-                () -> stats + " value -> " + s.toString()));
+        EnumSet.allOf(Statistic.class).forEach(s -> {
+            final boolean isSupported = computed.contains(s);
+            Assertions.assertEquals(isSupported, statistics.isSupported(s),
+                () -> stats + " isSupported -> " + s.toString());
+            if (isSupported) {
+                final StatisticResult result = expected.apply(expectedResult.get(s).get(id));
+                // Test individual values
+                TestHelper.assertDoubleEquals(result, () -> statistics.getAsDouble(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getAsDouble -> " + s.toString());
+                TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                TestHelper.assertLongEquals(result, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                TestHelper.assertBigIntegerEquals(result, () -> statistics.getAsBigInteger(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getAsBigInteger -> " + s.toString());
+                // Test the values from the result
+                TestHelper.assertEquals(result, statistics.getResult(s),
+                    DoubleTolerances.equals(),
+                    () -> stats + " getResult -> " + s.toString());
+            } else {
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsDouble(s),
+                    () -> stats + " getAsDouble -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsLong(s),
+                    () -> stats + " getAsLong -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getAsBigInteger(s),
+                    () -> stats + " getAsBigInteger -> " + s.toString());
+                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getResult(s),
+                    () -> stats + " getResult -> " + s.toString());
+            }
+        });
     }
 
     /**
      * Add all the {@code values} to an aggregator of the {@code statistics}.
      *
-     * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
-     * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
+     * <p>This method verifies that the {@link LongStatistics#getAsDouble(Statistic)} and
+     * {@link LongStatistics#getResult(Statistic)} methods return the same
      * result as values are added.
      *
-     * @param statistic Statistics.
+     * @param statistics Statistics.
      * @param values Values.
      * @return the statistics
      */
-    private static DoubleStatistics acceptAll(Statistic[] statistics, double[] values) {
-        final DoubleStatistics stats = DoubleStatistics.of(statistics);
-        final DoubleSupplier[] f = getSuppliers(statistics, stats);
-        for (final double x : values) {
+    private static LongStatistics acceptAll(Statistic[] statistics, long[] values) {
+        final LongStatistics stats = LongStatistics.of(statistics);
+        final StatisticResult[] f = getResults(statistics, stats);
+        for (final long x : values) {
             stats.accept(x);
             for (int i = 0; i < statistics.length; i++) {
                 final Statistic s = statistics[i];
-                Assertions.assertEquals(stats.get(s), f[i].getAsDouble(),
-                    () -> "Supplier(" + s + ") after value " + x);
+                Assertions.assertEquals(stats.getAsDouble(s), f[i].getAsDouble(),
+                    () -> "StatisticResult(" + s + ") after value " + x);
             }
         }
         return stats;
     }
 
     /**
-     * Gets the suppliers for the {@code statistics}.
+     * Gets the results for the {@code statistics}.
      *
      * @param statistics Statistics to compute.
      * @param stats Statistic aggregator.
-     * @return the suppliers
+     * @return the results
      */
-    private static DoubleSupplier[] getSuppliers(Statistic[] statistics, final DoubleStatistics stats) {
-        final DoubleSupplier[] f = new DoubleSupplier[statistics.length];
+    private static StatisticResult[] getResults(Statistic[] statistics, final LongStatistics stats) {
+        final StatisticResult[] f = new StatisticResult[statistics.length];
         for (int i = 0; i < statistics.length; i++) {
-            final DoubleSupplier supplier = stats.getSupplier(statistics[i]);
-            Assertions.assertFalse(supplier instanceof DoubleStatistic,
-                () -> "DoubleStatistic instance: " + supplier.getClass().getSimpleName());
+            final StatisticResult supplier = stats.getResult(statistics[i]);
+            Assertions.assertFalse(supplier instanceof LongStatistic,
+                () -> "LongStatistic instance: " + supplier.getClass().getSimpleName());
             f[i] = supplier;
         }
         return f;
@@ -342,153 +418,115 @@ class DoubleStatisticsTest {
     /**
      * Combine the two statistic aggregators.
      *
-     * <p>This method verifies that the {@link DoubleStatistics#get(Statistic)} and
-     * {@link DoubleStatistics#getSupplier(Statistic)} methods return the same
-     * result after the {@link DoubleStatistics#combine(DoubleStatistics)}.
+     * <p>This method verifies that the {@link LongStatistics#getAsDouble(Statistic)} and
+     * {@link LongStatistics#getResult(Statistic)} methods return the same
+     * result after the {@link LongStatistics#combine(LongStatistics)}.
      *
      * @param statistics Statistics to compute.
      * @param s1 Statistic aggregator.
      * @param s2 Statistic aggregator.
-     * @return the double statistics
+     * @return the combined LongStatistics
      */
-    private static DoubleStatistics combine(Statistic[] statistics,
-        DoubleStatistics s1, DoubleStatistics s2) {
-        final DoubleSupplier[] f = getSuppliers(statistics, s1);
+    private static LongStatistics combine(Statistic[] statistics,
+        LongStatistics s1, LongStatistics s2) {
+        final StatisticResult[] f = getResults(statistics, s1);
         s1.combine(s2);
         for (int i = 0; i < statistics.length; i++) {
             final Statistic s = statistics[i];
-            Assertions.assertEquals(s1.get(s), f[i].getAsDouble(),
-                () -> "Supplier(" + s + ") after combine");
+            Assertions.assertEquals(s1.getAsDouble(s), f[i].getAsDouble(),
+                () -> "StatisticResult(" + s + ") after combine");
         }
         return s1;
     }
 
-    @Test
-    void testNoOpConsumer() {
-        final DoubleConsumer c = DoubleStatistics.NOOP;
-        // Hit coverage
-        c.accept(0);
-        final double[] value = {0};
-        final DoubleConsumer other = x -> value[0] = x;
-        final DoubleConsumer combined = c.andThen(other);
-        Assertions.assertSame(combined, other);
-        final double y = 42;
-        combined.accept(y);
-        Assertions.assertEquals(y, value[0]);
-    }
-
     @Test
     void testOfThrows() {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of());
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(EMPTY_STATISTIC_ARRAY));
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(new Statistic[1]));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of());
+        Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of(EMPTY_STATISTIC_ARRAY));
+        Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(new Statistic[1]));
     }
 
     @Test
     void testOfSetThrows() {
         final EnumSet<Statistic> s1 = EnumSet.noneOf(Statistic.class);
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.of(s1));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.of(s1));
         final EnumSet<Statistic> s2 = null;
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s2));
+        Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(s2));
         final EnumSet<Statistic> s3 = EnumSet.of(Statistic.MIN);
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.of(s3, null));
+        final long[] nullValues = null;
+        Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.of(s3, nullValues));
     }
 
     @Test
     void testBuilderThrows() {
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder());
-        Assertions.assertThrows(IllegalArgumentException.class, () -> DoubleStatistics.builder(EMPTY_STATISTIC_ARRAY));
-        Assertions.assertThrows(NullPointerException.class, () -> DoubleStatistics.builder(new Statistic[1]));
+        Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.builder());
+        Assertions.assertThrows(IllegalArgumentException.class, () -> LongStatistics.builder(EMPTY_STATISTIC_ARRAY));
+        Assertions.assertThrows(NullPointerException.class, () -> LongStatistics.builder(new Statistic[1]));
     }
 
     @Test
     void testIsSupportedWithNull() {
-        DoubleStatistics s = DoubleStatistics.of(Statistic.MIN);
+        LongStatistics s = LongStatistics.of(Statistic.MIN);
         Assertions.assertThrows(NullPointerException.class, () -> s.isSupported(null));
     }
 
-    @ParameterizedTest
-    @MethodSource
-    void testNotSupported(Statistic stat) {
-        DoubleStatistics statistics = DoubleStatistics.of(stat);
-        for (final Statistic s : Statistic.values()) {
-            Assertions.assertEquals(s == stat, statistics.isSupported(s),
-                () -> stat + " isSupported -> " + s.toString());
-            if (s == stat) {
-                Assertions.assertDoesNotThrow(() -> statistics.get(s),
-                    () -> stat + " get -> " + s.toString());
-                Assertions.assertNotNull(statistics.getSupplier(s),
-                    () -> stat + " getSupplier -> " + s.toString());
-            } else {
-                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.get(s),
-                    () -> stat + " get -> " + s.toString());
-                Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.getSupplier(s),
-                    () -> stat + " getSupplier -> " + s.toString());
-            }
-        }
-    }
-
-    static Statistic[] testNotSupported() {
-        return new Statistic[] {Statistic.MIN, Statistic.PRODUCT};
-    }
-
     @ParameterizedTest
     @MethodSource
     void testIncompatibleCombineThrows(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
-        final double[] v1 = {1, 2, 3.5, 6};
-        final double[] v2 = {3, 4, 5};
-        DoubleStatistics statistics = DoubleStatistics.of(stat1, v1);
-        DoubleStatistics other = DoubleStatistics.of(stat2, v2);
+        final long[] v1 = {1, 2, 4, 6};
+        final long[] v2 = {3, 4, 5};
+        LongStatistics statistics = LongStatistics.of(stat1, v1);
+        LongStatistics other = LongStatistics.of(stat2, v2);
         // Store values
-        final double[] values = stat1.stream().mapToDouble(statistics::get).toArray();
+        final double[] values = stat1.stream().mapToDouble(statistics::getAsDouble).toArray();
         Assertions.assertThrows(IllegalArgumentException.class, () -> statistics.combine(other),
             () -> stat1 + " " + stat2);
         // Values should be unchanged
         final int[] i = {0};
         stat1.stream().forEach(
-            s -> Assertions.assertEquals(values[i[0]++], statistics.get(s), () -> s + " changed"));
+            s -> Assertions.assertEquals(values[i[0]++], statistics.getAsDouble(s), () -> s + " changed"));
     }
 
     static Stream<Arguments> testIncompatibleCombineThrows() {
         return Stream.of(
             Arguments.of(EnumSet.of(Statistic.MIN), EnumSet.of(Statistic.PRODUCT)),
             Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MIN)),
-            // Note: MEAN is compatible with VARIANCE. The combine is not symmetric.
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.MEAN))
+            // Note: SKEWNESS is compatible with KURTOSIS. The combine is not symmetric.
+            Arguments.of(EnumSet.of(Statistic.KURTOSIS), EnumSet.of(Statistic.SKEWNESS))
         );
     }
 
     @ParameterizedTest
     @MethodSource
     void testCompatibleCombine(EnumSet<Statistic> stat1, EnumSet<Statistic> stat2) {
-        final double[] v1 = {1, 2, 3.5, 6};
-        final double[] v2 = {3, 4, 5};
-        final DoubleStatistics statistics1 = DoubleStatistics.of(stat1, v1);
-        final DoubleStatistics statistics2 = DoubleStatistics.of(stat1, v1);
+        final long[] v1 = {1, 2, 4, 6};
+        final long[] v2 = {3, 4, 5};
+        final LongStatistics statistics1 = LongStatistics.of(stat1, v1);
+        final LongStatistics statistics2 = LongStatistics.of(stat1, v1);
         // Note: other1 is intentionally using the same flags as statistics1
-        final DoubleStatistics other1 = DoubleStatistics.of(stat1, v2);
-        final DoubleStatistics other2 = DoubleStatistics.of(stat2, v2);
+        final LongStatistics other1 = LongStatistics.of(stat1, v2);
+        final LongStatistics other2 = LongStatistics.of(stat2, v2);
         // This should work
         statistics1.combine(other1);
         // This should be compatible
         statistics2.combine(other2);
         // The stats should be the same
         for (final Statistic s : stat1) {
-            final double expected = statistics1.get(s);
+            final double expected = statistics1.getAsDouble(s);
             assertFinite(expected, s);
-            Assertions.assertEquals(expected, statistics2.get(s), () -> s.toString());
+            Assertions.assertEquals(expected, statistics2.getAsDouble(s), () -> s.toString());
         }
     }
 
     static Stream<Arguments> testCompatibleCombine() {
         return Stream.of(
             Arguments.of(EnumSet.of(Statistic.MEAN), EnumSet.of(Statistic.VARIANCE)),
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SKEWNESS)),
-            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.KURTOSIS)),
+            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.SUM, Statistic.SUM_OF_SQUARES)),
+            Arguments.of(EnumSet.of(Statistic.VARIANCE), EnumSet.of(Statistic.STANDARD_DEVIATION)),
             Arguments.of(EnumSet.of(Statistic.GEOMETRIC_MEAN), EnumSet.of(Statistic.SUM_OF_LOGS)),
             // Compatible combinations
             Arguments.of(EnumSet.of(Statistic.VARIANCE, Statistic.MIN, Statistic.SKEWNESS),
-                         EnumSet.of(Statistic.KURTOSIS, Statistic.MEAN, Statistic.MIN))
+                         EnumSet.of(Statistic.KURTOSIS, Statistic.SUM_OF_SQUARES, Statistic.MEAN, Statistic.MIN))
         );
     }
 
@@ -498,8 +536,8 @@ class DoubleStatisticsTest {
 
     @ParameterizedTest
     @MethodSource
-    void testBiased(Statistic stat, double[] values, boolean[] options, double[] results) {
-        final DoubleStatistics statistics1 = DoubleStatistics.builder(stat).build(values);
+    void testBiased(Statistic stat, long[] values, boolean[] options, double[] results) {
+        final LongStatistics statistics1 = LongStatistics.builder(stat).build(values);
 
         StatisticsConfiguration c = StatisticsConfiguration.withDefaults();
         DoubleSupplier s = null;
@@ -510,32 +548,32 @@ class DoubleStatisticsTest {
             c = c.withBiased(value);
             Assertions.assertSame(statistics1, statistics1.setConfiguration(c));
 
-            Assertions.assertEquals(results[i], statistics1.get(stat),
-                () -> options[i] + " get: " + BaseDoubleStatisticTest.format(values));
-            final DoubleSupplier s1 = statistics1.getSupplier(stat);
+            Assertions.assertEquals(results[i], statistics1.getAsDouble(stat),
+                () -> options[i] + " get: " + BaseLongStatisticTest.format(values));
+            final DoubleSupplier s1 = statistics1.getResult(stat);
             Assertions.assertEquals(results[i], s1.getAsDouble(),
-                () -> options[i] + " supplier: " + BaseDoubleStatisticTest.format(values));
+                () -> options[i] + " supplier: " + BaseLongStatisticTest.format(values));
 
             // Config change does not propagate to previous supplier
             if (s != null) {
                 final int j = (i - 1 + options.length) % options.length;
                 Assertions.assertEquals(results[j], s.getAsDouble(),
-                    () -> options[j] + " previous supplier: " + BaseDoubleStatisticTest.format(values));
+                    () -> options[j] + " previous supplier: " + BaseLongStatisticTest.format(values));
             }
             s = s1;
 
             // Set through the builder
-            final DoubleStatistics statistics2 = DoubleStatistics.builder(stat)
+            final LongStatistics statistics2 = LongStatistics.builder(stat)
                 .setConfiguration(c).build(values);
-            Assertions.assertEquals(results[i], statistics2.get(stat),
-                () -> options[i] + " get via builder: " + BaseDoubleStatisticTest.format(values));
+            Assertions.assertEquals(results[i], statistics2.getAsDouble(stat),
+                () -> options[i] + " get via builder: " + BaseLongStatisticTest.format(values));
         }
     }
 
     static Stream<Arguments> testBiased() {
         final Stream.Builder<Arguments> builder = Stream.builder();
         // Data must generate different results for different options
-        final double[][] data = {
+        final long[][] data = {
             {1},
             {1, 2},
             {1, 2, 4},
@@ -544,10 +582,10 @@ class DoubleStatisticsTest {
         };
         final Statistic[] stats = {Statistic.STANDARD_DEVIATION, Statistic.VARIANCE,
             Statistic.SKEWNESS, Statistic.KURTOSIS};
-        final int[] diff = new int[stats.length];
-        for (final double[] d : data) {
-            diff[0] += addBooleanOptionCase(builder, d, stats[0], StandardDeviation::of, StandardDeviation::setBiased);
-            diff[1] += addBooleanOptionCase(builder, d, stats[1], Variance::of, Variance::setBiased);
+        final long[] diff = new long[stats.length];
+        for (final long[] d : data) {
+            diff[0] += addBooleanOptionCase(builder, d, stats[0], LongStandardDeviation::of, LongStandardDeviation::setBiased);
+            diff[1] += addBooleanOptionCase(builder, d, stats[1], LongVariance::of, LongVariance::setBiased);
             diff[2] += addBooleanOptionCase(builder, d, stats[2], Skewness::of, Skewness::setBiased);
             diff[3] += addBooleanOptionCase(builder, d, stats[3], Kurtosis::of, Kurtosis::setBiased);
         }
@@ -566,7 +604,7 @@ class DoubleStatisticsTest {
      * <p>This method returns the number of unique results. It does not enforce that the
      * results are different.
      *
-     * @param <T> {@link DoubleStatistic} being computed.
+     * @param <T> {@link StatisticResult} being computed.
      * @param builder Argument builder.
      * @param values Values.
      * @param statistic Statistic.
@@ -574,9 +612,9 @@ class DoubleStatisticsTest {
      * @param setter Option setter.
      * @return the number of unique results
      */
-    private static <T extends DoubleStatistic & StatisticAccumulator<T>> int addBooleanOptionCase(
-            Stream.Builder<Arguments> builder, double[] values, Statistic statistic,
-            Function<double[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
+    private static <T extends StatisticResult & StatisticAccumulator<T>> int addBooleanOptionCase(
+            Stream.Builder<Arguments> builder, long[] values, Statistic statistic,
+            Function<long[], T> arrayConstructor, BiConsumer<T, Boolean> setter) {
         final T stat = arrayConstructor.apply(values);
         final boolean[] options = {true, false};
         final double[] results = new double[2];
@@ -589,4 +627,12 @@ class DoubleStatisticsTest {
         builder.accept(Arguments.of(statistic, values, options, results));
         return all.size();
     }
+
+    @Test
+    void testMoments() {
+        // Hits coverage within the builder for when kurtosis is ordered before skewness
+        final LongStatistics s = LongStatistics.of(Statistic.KURTOSIS, Statistic.SKEWNESS);
+        Assertions.assertTrue(s.isSupported(Statistic.KURTOSIS));
+        Assertions.assertTrue(s.isSupported(Statistic.SKEWNESS));
+    }
 }
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java
new file mode 100644
index 0000000..d1dc7e1
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/StatisticsTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.util.function.DoubleConsumer;
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link Statistics} utilities.
+ */
+class StatisticsTest {
+    @Test
+    void testNoOpDoubleConsumer() {
+        final DoubleConsumer c = Statistics.DOUBLE_NOOP;
+        // Hit coverage
+        c.accept(0);
+        final double[] value = {0};
+        final DoubleConsumer other = x -> value[0] = x;
+        final DoubleConsumer combined = c.andThen(other);
+        Assertions.assertSame(combined, other);
+        final double y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, value[0]);
+    }
+
+    @Test
+    void testComposeDoubleConsumers() {
+        Assertions.assertNull(Statistics.compose((DoubleConsumer) null));
+        Assertions.assertNull(Statistics.compose((DoubleConsumer) null, (DoubleConsumer) null));
+        final double[] v1 = {0};
+        final double[] v2 = {0};
+        final DoubleConsumer c1 = x -> v1[0] = x;
+        final DoubleConsumer c2 = x -> v2[0] = x;
+        final DoubleConsumer combined = Statistics.compose(c1, c2);
+        final double y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, v1[0]);
+        Assertions.assertEquals(y, v2[0]);
+    }
+
+    @Test
+    void testNoOpIntConsumer() {
+        final IntConsumer c = Statistics.INT_NOOP;
+        // Hit coverage
+        c.accept(0);
+        final int[] value = {0};
+        final IntConsumer other = x -> value[0] = x;
+        final IntConsumer combined = c.andThen(other);
+        Assertions.assertSame(combined, other);
+        final int y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, value[0]);
+    }
+
+    @Test
+    void testComposeIntConsumers() {
+        Assertions.assertNull(Statistics.compose((IntConsumer) null));
+        Assertions.assertNull(Statistics.compose((IntConsumer) null, (IntConsumer) null));
+        final int[] v1 = {0};
+        final int[] v2 = {0};
+        final IntConsumer c1 = x -> v1[0] = x;
+        final IntConsumer c2 = x -> v2[0] = x;
+        final IntConsumer combined = Statistics.compose(c1, c2);
+        final int y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, v1[0]);
+        Assertions.assertEquals(y, v2[0]);
+    }
+
+    @Test
+    void testNoOpLongConsumer() {
+        final LongConsumer c = Statistics.LONG_NOOP;
+        // Hit coverage
+        c.accept(0);
+        final long[] value = {0};
+        final LongConsumer other = x -> value[0] = x;
+        final LongConsumer combined = c.andThen(other);
+        Assertions.assertSame(combined, other);
+        final long y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, value[0]);
+    }
+
+    @Test
+    void testComposeLongConsumers() {
+        Assertions.assertNull(Statistics.compose((LongConsumer) null));
+        Assertions.assertNull(Statistics.compose((LongConsumer) null, (LongConsumer) null));
+        final long[] v1 = {0};
+        final long[] v2 = {0};
+        final LongConsumer c1 = x -> v1[0] = x;
+        final LongConsumer c2 = x -> v2[0] = x;
+        final LongConsumer combined = Statistics.compose(c1, c2);
+        final long y = 42;
+        combined.accept(y);
+        Assertions.assertEquals(y, v1[0]);
+        Assertions.assertEquals(y, v2[0]);
+    }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
index a1a57e5..11485c2 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/TestHelper.java
@@ -20,6 +20,9 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
 import java.util.Arrays;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 import org.apache.commons.numbers.core.DD;
 import org.apache.commons.rng.UniformRandomProvider;
@@ -406,6 +409,24 @@ final class TestHelper {
      */
     static void assertEquals(StatisticResult expected, StatisticResult actual, DoubleTolerance tol,
             Supplier<String> msg) {
+        assertIntEquals(expected, actual::getAsInt, msg);
+        assertLongEquals(expected, actual::getAsLong, msg);
+        assertDoubleEquals(expected, actual::getAsDouble, tol, msg);
+        assertBigIntegerEquals(expected, actual::getAsBigInteger, tol, msg);
+    }
+
+    /**
+     * Assert the results are equal.
+     *
+     * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+     * to throw an exception of the same class.
+     *
+     * @param expected Expected.
+     * @param actual Actual.
+     * @param msg Message used for failure.
+     */
+    static void assertIntEquals(StatisticResult expected, IntSupplier actual,
+            Supplier<String> msg) {
         final Supplier<String> intMsg = () -> prefix(msg) + " int value";
         Integer i = null;
         try {
@@ -416,7 +437,20 @@ final class TestHelper {
         if (i != null) {
             Assertions.assertEquals(i.intValue(), actual.getAsInt(), intMsg);
         }
+    }
 
+    /**
+     * Assert the results are equal.
+     *
+     * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+     * to throw an exception of the same class.
+     *
+     * @param expected Expected.
+     * @param actual Actual.
+     * @param msg Message used for failure.
+     */
+    static void assertLongEquals(StatisticResult expected, LongSupplier actual,
+            Supplier<String> msg) {
         final Supplier<String> longMsg = () -> prefix(msg) + " long value";
         Long l = null;
         try {
@@ -427,7 +461,26 @@ final class TestHelper {
         if (l != null) {
             Assertions.assertEquals(l.longValue(), actual.getAsLong(), longMsg);
         }
+    }
 
+    /**
+     * Assert the results are equal.
+     *
+     * <p>If the tolerance is null then it is assumed the result is an exact integer type;
+     * the double value is compared using binary equality.
+     *
+     * <p>If the tolerance is not null then it is assumed the result is a {@code double} type.
+     *
+     * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+     * to throw an exception of the same class.
+     *
+     * @param expected Expected.
+     * @param actual Actual.
+     * @param tol Tolerance for double equality.
+     * @param msg Message used for failure.
+     */
+    static void assertDoubleEquals(StatisticResult expected, DoubleSupplier actual, DoubleTolerance tol,
+            Supplier<String> msg) {
         final Supplier<String> doubleMsg = () -> prefix(msg) + " double value";
         Double d = null;
         try {
@@ -442,21 +495,42 @@ final class TestHelper {
                 TestUtils.assertEquals(d, actual.getAsDouble(), tol, doubleMsg);
             }
         }
+    }
 
+    /**
+     * Assert the results are equal.
+     *
+     * <p>If the tolerance is null then it is assumed the result is an exact integer type;
+     * the double value is compared using binary equality.
+     *
+     * <p>If the tolerance is not null then it is assumed the result is a {@code double} type;
+     * the BigInteger value is compared using its {@code double} representation if the
+     * actual BigInteger result is not equal.
+     *
+     * <p>If the {@code expected} throws an exception, then the {@code actual} is asserted
+     * to throw an exception of the same class.
+     *
+     * @param expected Expected.
+     * @param actual Actual.
+     * @param tol Tolerance for double equality.
+     * @param msg Message used for failure.
+     */
+    static void assertBigIntegerEquals(StatisticResult expected, Supplier<BigInteger> actual, DoubleTolerance tol,
+            Supplier<String> msg) {
         final Supplier<String> bigMsg = () -> prefix(msg) + " BigInteger value";
         BigInteger b = null;
         try {
             b = expected.getAsBigInteger();
         } catch (Throwable t) {
-            Assertions.assertThrowsExactly(t.getClass(), () -> actual.getAsBigInteger(), bigMsg);
+            Assertions.assertThrowsExactly(t.getClass(), () -> actual.get(), bigMsg);
         }
         if (b != null) {
             if (tol == null) {
                 // Assume exact
-                Assertions.assertEquals(b, actual.getAsBigInteger(), bigMsg);
+                Assertions.assertEquals(b, actual.get(), bigMsg);
             } else {
                 // Double computation may not be exact so check within tolerance
-                final BigInteger bb = actual.getAsBigInteger();
+                final BigInteger bb = actual.get();
                 if (!b.equals(bb)) {
                     TestUtils.assertEquals(b.doubleValue(), bb.doubleValue(), tol, bigMsg);
                 }
diff --git a/src/conf/checkstyle/checkstyle-suppressions.xml b/src/conf/checkstyle/checkstyle-suppressions.xml
index dd425bc..e5d1b95 100644
--- a/src/conf/checkstyle/checkstyle-suppressions.xml
+++ b/src/conf/checkstyle/checkstyle-suppressions.xml
@@ -23,7 +23,7 @@
   <suppress checks="LocalFinalVariableName" files=".*[/\\]ZipfDistribution.java" />
   <suppress checks="LocalFinalVariableName" files=".*[/\\]HypergeometricDistribution.java" />
   <suppress checks="ParameterNumber" files=".*[/\\]TTest.java" />
-  <suppress checks="ParameterNumber" files=".*[/\\]DoubleStatistics.java" />
+  <suppress checks="ParameterNumber" files=".*[/\\](Double|Int|Long)Statistics.java" />
   <!-- Be more lenient on tests. -->
   <suppress checks="Javadoc" files=".*[/\\]test[/\\].*" />
   <suppress checks="MultipleStringLiterals" files=".*[/\\]test[/\\].*" />
@@ -39,7 +39,7 @@
   <suppress checks="ParameterNumber" files=".*[/\\]WilcoxonSignedRankTestTest.java" />
   <suppress checks="ParameterNumber" files=".*[/\\]UnconditionedExactTestTest.java" />
   <suppress checks="MethodLength" files=".*[/\\]WilcoxonSignedRankTestTest.java" />
-  <suppress checks="IllegalCatch" files=".*[/\\]TestHelper.java" lines="390-450" />
+  <suppress checks="IllegalCatch" files=".*[/\\]TestHelper.java" lines="400-550" />
   <suppress checks="IllegalCatch" files=".*[/\\]BaseStatisticTest.java" lines="280-400" />
   <suppress checks="IllegalCatch" files=".*[/\\]IntMathTest.java" lines="170-185" />
 </suppressions>
diff --git a/src/conf/pmd/pmd-ruleset.xml b/src/conf/pmd/pmd-ruleset.xml
index 72d7b54..87c02d0 100644
--- a/src/conf/pmd/pmd-ruleset.xml
+++ b/src/conf/pmd/pmd-ruleset.xml
@@ -167,7 +167,8 @@
           or @SimpleName='KolmogorovSmirnovTest' or @SimpleName='DD' or @SimpleName='Arguments'
           or @SimpleName='MannWhitneyUTest' or @SimpleName='WilcoxonSignedRankTest'
           or @SimpleName='HypergeometricDistribution' or @SimpleName='UnconditionedExactTest'
-           or @SimpleName='DoubleStatistics']"/>
+          or @SimpleName='DoubleStatistics' or @SimpleName='IntStatistics'
+          or @SimpleName='LongStatistics']"/>
     </properties>
   </rule>
   <rule ref="category/java/design.xml/LogicInversion">