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 2023/12/23 12:19:26 UTC
(commons-statistics) 03/06: STATISTICS-81: Add integer mean and variance implementation
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 3fda89532f8d49953ce41f9d79155ebbcfdd68a7
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Wed Dec 20 23:14:36 2023 +0000
STATISTICS-81: Add integer mean and variance implementation
Requires specialised aggregator classes for exact integer arithmetic.
Add initial benchmark of integer based statistics.
---
.github/workflows/maven.yml | 14 +-
.../commons/statistics/descriptive/Int128.java | 237 +++
.../commons/statistics/descriptive/IntMath.java | 391 +++++
.../commons/statistics/descriptive/IntMean.java | 144 ++
.../statistics/descriptive/IntVariance.java | 248 +++
.../commons/statistics/descriptive/LongMean.java | 141 ++
.../statistics/descriptive/LongVariance.java | 226 +++
.../commons/statistics/descriptive/UInt128.java | 238 +++
.../commons/statistics/descriptive/UInt192.java | 247 +++
.../commons/statistics/descriptive/UInt96.java | 155 ++
.../descriptive/BaseIntStatisticTest.java | 20 +-
.../descriptive/BaseLongStatisticTest.java | 50 +-
.../statistics/descriptive/BaseStatisticTest.java | 32 +-
.../commons/statistics/descriptive/Int128Test.java | 173 ++
.../statistics/descriptive/IntMathTest.java | 206 +++
.../statistics/descriptive/IntMeanTest.java | 116 ++
.../statistics/descriptive/IntVarianceTest.java | 191 +++
.../statistics/descriptive/LongMeanTest.java | 124 ++
.../statistics/descriptive/LongVarianceTest.java | 203 +++
.../commons/statistics/descriptive/TestHelper.java | 128 ++
.../statistics/descriptive/UInt128Test.java | 239 +++
.../statistics/descriptive/UInt192Test.java | 208 +++
.../commons/statistics/descriptive/UInt96Test.java | 140 ++
commons-statistics-examples/examples-jmh/pom.xml | 25 +-
.../examples/jmh/descriptive/Int128.java | 276 ++++
.../examples/jmh/descriptive/IntMath.java | 309 ++++
.../jmh/descriptive/IntMomentPerformance.java | 1719 ++++++++++++++++++++
.../examples/jmh/descriptive/LongVariance2.java | 196 +++
.../examples/jmh/descriptive/UInt128.java | 285 ++++
.../examples/jmh/descriptive/UInt192.java | 297 ++++
.../examples/jmh/descriptive/UInt96.java | 170 ++
.../examples/jmh/descriptive/Int128Test.java | 194 +++
.../examples/jmh/descriptive/IntMathTest.java | 152 ++
.../examples/jmh/descriptive/LongVarianceTest.java | 47 +
.../examples/jmh/descriptive/TestUtils.java | 159 ++
.../examples/jmh/descriptive/UInt128Test.java | 238 +++
.../examples/jmh/descriptive/UInt192Test.java | 206 +++
.../examples/jmh/descriptive/UInt96Test.java | 150 ++
src/conf/checkstyle/checkstyle-suppressions.xml | 1 +
39 files changed, 8240 insertions(+), 55 deletions(-)
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index e033075..fbef6c1 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -38,6 +38,16 @@ jobs:
distribution: temurin
java-version: ${{ matrix.java }}
cache: 'maven'
- - name: Build with Maven including examples
+ - name: Build with Maven
# Use the default goal
- run: mvn --show-version --batch-mode --no-transfer-progress -P examples
+ if: matrix.java == 8
+ run: mvn --show-version --batch-mode --no-transfer-progress
+ - name: Build with Maven including examples
+ # Examples require Java 11+.
+ # Building javadoc errors when run with the package phase with an error about
+ # the module path.
+ # Here we run the build and javadoc generation separately (which requires install of sources)
+ if: matrix.java > 8
+ run: |
+ mvn --show-version --batch-mode --no-transfer-progress -P examples clean install -Dmaven.javadoc.skip
+ mvn -P examples javadoc:javadoc -Dmaven.javadoc.skip=false
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
new file mode 100644
index 0000000..5ae6357
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/Int128.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import org.apache.commons.numbers.core.DD;
+
+/**
+ * A mutable 128-bit signed integer.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values.
+ *
+ * <p>Note: This number uses a signed long integer representation of:
+ *
+ * <pre>value = 2<sup>64</sup> * hi64 + lo64</pre>
+ *
+ * <p>If the high value is zero then the low value is the long representation of the
+ * number including the sign bit. Otherwise the low value corresponds to a correction
+ * term for the scaled high value which contains the sign-bit of the number.
+ *
+ * @since 1.1
+ */
+final class Int128 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ /** low 64-bits. */
+ private long lo;
+ /** high 64-bits. */
+ private long hi;
+
+ /**
+ * Create an instance.
+ */
+ private Int128() {
+ // No-op
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param x Value.
+ */
+ private Int128(long x) {
+ lo = x;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ */
+ Int128(long hi, long lo) {
+ this.lo = lo;
+ this.hi = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static Int128 create() {
+ return new Int128();
+ }
+
+ /**
+ * Create an instance of the {@code long} value.
+ *
+ * @param x Value.
+ * @return the instance
+ */
+ static Int128 of(long x) {
+ return new Int128(x);
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(long x) {
+ final long y = lo;
+ final long r = y + x;
+ // Overflow if the result has the opposite sign of both arguments
+ // (+,+) -> -
+ // (-,-) -> +
+ // Detect opposite sign:
+ if (((y ^ r) & (x ^ r)) < 0) {
+ // Carry overflow bit
+ hi += x < 0 ? -1 : 1;
+ }
+ lo = r;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(Int128 x) {
+ // Avoid issues adding to itself
+ final long l = x.lo;
+ final long h = x.hi;
+ add(l);
+ hi += h;
+ }
+
+ /**
+ * Compute the square of the low 64-bits of this number.
+ *
+ * <p>Warning: This ignores the upper 64-bits. Use with caution.
+ *
+ * @return the square
+ */
+ UInt128 squareLow() {
+ final long x = lo;
+ final long upper = IntMath.squareHigh(x);
+ return new UInt128(upper, x * x);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ long h = hi;
+ long l = lo;
+ // Special cases
+ if (h == 0) {
+ return BigInteger.valueOf(l);
+ }
+ if (l == 0) {
+ return BigInteger.valueOf(h).shiftLeft(64);
+ }
+
+ // The representation is 2^64 * hi64 + lo64.
+ // Here we avoid evaluating the addition:
+ // BigInteger.valueOf(l).add(BigInteger.valueOf(h).shiftLeft(64))
+ // It is faster to create from bytes.
+ // BigInteger bytes are an unsigned integer in BigEndian format, plus a sign.
+ // If both values are positive we can use the values unchanged.
+ // Otherwise selective negation is used to create a positive magnitude
+ // and we track the sign.
+ // Note: Negation of -2^63 is valid to create an unsigned 2^63.
+
+ int sign = 1;
+ if ((h ^ l) < 0) {
+ // Opposite signs and lo64 is not zero.
+ // The lo64 bits are an adjustment to the magnitude of hi64
+ // to make it smaller.
+ // Here we rearrange to [2^64 * (hi64-1)] + [2^64 - lo64].
+ // The second term [2^64 - lo64] can use lo64 as an unsigned 64-bit integer.
+ // The first term [2^64 * (hi64-1)] does not work if low is zero.
+ // It would work if zero was detected and we carried the overflow
+ // bit up to h to make it equal to: (h - 1) + 1 == h.
+ // Instead lo64 == 0 is handled as a special case above.
+
+ if (h >= 0) {
+ // Treat (unchanged) low as an unsigned add
+ h = h - 1;
+ } else {
+ // As above with negation
+ h = ~h; // -h - 1
+ l = -l;
+ sign = -1;
+ }
+ } else if (h < 0) {
+ // Invert negative values to create the equivalent positive magnitude.
+ h = -h;
+ l = -l;
+ sign = -1;
+ }
+
+ return new BigInteger(sign,
+ ByteBuffer.allocate(Long.BYTES * 2)
+ .putLong(h).putLong(l).array());
+ }
+
+ /**
+ * Convert to a double-double.
+ *
+ * @return the value
+ */
+ DD toDD() {
+ // Don't combine two 64-bit DD numbers:
+ // DD.of(hi).scalb(64).add(DD.of(lo))
+ // It is more accurate to create a 96-bit number and add the final 32-bits.
+ // Sum low to high.
+ // Note: Masking a negative hi number will create a small positive magnitude
+ // to add to a larger negative number:
+ // e.g. x = (x & 0xff) + ((x >> 8) << 8)
+ return DD.of(lo).add((hi & MASK32) * 0x1.0p64).add((hi >> Integer.SIZE) * 0x1.0p96);
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * <p>If the high value is zero then the low value is the long representation of the
+ * number including the sign bit. Otherwise this value corresponds to a correction
+ * term for the scaled high value which contains the sign-bit of the number
+ * (see {@link Int128}).
+ *
+ * @return the low 64-bits
+ */
+ long lo64() {
+ return lo;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return the high 64-bits
+ * @see #lo64()
+ */
+ long hi64() {
+ return hi;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
new file mode 100644
index 0000000..714fc41
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMath.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * Support class for integer math.
+ *
+ * @since 1.1
+ */
+final class IntMath {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+ /** Mask for the lower 52-bits of a long. */
+ private static final long MASK52 = 0xf_ffff_ffff_ffffL;
+ /** Bias offset for the exponent of a double. */
+ private static final int EXP_BIAS = 1023;
+ /** Shift for the exponent of a double. */
+ private static final int EXP_SHIFT = 52;
+ /** 0.5. */
+ private static final double HALF = 0.5;
+
+ /** No instances. */
+ private IntMath() {}
+
+ /**
+ * Square the values as if an unsigned 64-bit long to produce the high 64-bits
+ * of the 128-bit unsigned result.
+ *a
+ * <p>This method computes the equivalent of:
+ * <pre>{@code
+ * Math.multiplyHigh(x, x)
+ * Math.unsignedMultiplyHigh(x, x) - (((x >> 63) & x) << 1)
+ * }</pre>
+ *
+ * <p>Note: The method {@code Math.multiplyHigh} was added in JDK 9
+ * and should be used as above when the source code targets Java 11
+ * to exploit the intrinsic method.
+ *
+ * <p>Note: The method uses the unsigned multiplication. When the input is negative
+ * it can be adjusted to the signed result by subtracting the argument twice from the
+ * result.
+ *
+ * @param x Value
+ * @return the high 64-bits of the 128-bit result
+ */
+ static long squareHigh(long x) {
+ // Computation is based on the following observation about the upper (a and x)
+ // and lower (b and y) bits of unsigned big-endian integers:
+ // ab * xy
+ // = b * y
+ // + b * x0
+ // + a0 * y
+ // + a0 * x0
+ // = b * y
+ // + b * x * 2^32
+ // + a * y * 2^32
+ // + a * x * 2^64
+ //
+ // Summation using a character for each byte:
+ //
+ // byby byby
+ // + bxbx bxbx 0000
+ // + ayay ayay 0000
+ // + axax axax 0000 0000
+ //
+ // The summation can be rearranged to ensure no overflow given
+ // that the result of two unsigned 32-bit integers multiplied together
+ // plus two full 32-bit integers cannot overflow 64 bits:
+ // > long x = (1L << 32) - 1
+ // > x * x + x + x == -1 (all bits set, no overflow)
+ //
+ // The carry is a composed intermediate which will never overflow:
+ //
+ // byby byby
+ // + bxbx 0000
+ // + ayay ayay 0000
+ //
+ // + bxbx 0000 0000
+ // + axax axax 0000 0000
+
+ final long a = x >>> 32;
+ final long b = x & MASK32;
+
+ final long aa = a * a;
+ final long ab = a * b;
+ final long bb = b * b;
+
+ // Cannot overflow
+ final long carry = (bb >>> 32) +
+ (ab & MASK32) +
+ ab;
+ // Note:
+ // low = (carry << 32) | (bb & MASK32)
+ // Benchmarking shows outputting low to a long[] output argument
+ // has no benefit over computing 'low = value * value' separately.
+
+ final long hi = (ab >>> 32) + (carry >>> 32) + aa;
+ // Adjust to the signed result:
+ // if x < 0:
+ // hi - 2 * x
+ return hi - (((x >> 63) & x) << 1);
+ }
+
+ /**
+ * Multiply the two values as if unsigned 64-bit longs to produce the high 64-bits
+ * of the 128-bit unsigned result.
+ *
+ * <p>This method computes the equivalent of:
+ * <pre>{@code
+ * Math.multiplyHigh(a, b) + ((a >> 63) & b) + ((b >> 63) & a)
+ * }</pre>
+ *
+ * <p>Note: The method {@code Math.multiplyHigh} was added in JDK 9
+ * and should be used as above when the source code targets Java 11
+ * to exploit the intrinsic method.
+ *
+ * <p>Note: The method {@code Math.unsignedMultiplyHigh} was added in JDK 18
+ * and should be used when the source code target allows.
+ *
+ * <p>Taken from {@code o.a.c.rng.core.source64.LXMSupport}.
+ *
+ * @param value1 the first value
+ * @param value2 the second value
+ * @return the high 64-bits of the 128-bit result
+ */
+ static long unsignedMultiplyHigh(long value1, long value2) {
+ // Computation is based on the following observation about the upper (a and x)
+ // and lower (b and y) bits of unsigned big-endian integers:
+ // ab * xy
+ // = b * y
+ // + b * x0
+ // + a0 * y
+ // + a0 * x0
+ // = b * y
+ // + b * x * 2^32
+ // + a * y * 2^32
+ // + a * x * 2^64
+ //
+ // Summation using a character for each byte:
+ //
+ // byby byby
+ // + bxbx bxbx 0000
+ // + ayay ayay 0000
+ // + axax axax 0000 0000
+ //
+ // The summation can be rearranged to ensure no overflow given
+ // that the result of two unsigned 32-bit integers multiplied together
+ // plus two full 32-bit integers cannot overflow 64 bits:
+ // > long x = (1L << 32) - 1
+ // > x * x + x + x == -1 (all bits set, no overflow)
+ //
+ // The carry is a composed intermediate which will never overflow:
+ //
+ // byby byby
+ // + bxbx 0000
+ // + ayay ayay 0000
+ //
+ // + bxbx 0000 0000
+ // + axax axax 0000 0000
+
+ final long a = value1 >>> 32;
+ final long b = value1 & MASK32;
+ final long x = value2 >>> 32;
+ final long y = value2 & MASK32;
+
+ final long by = b * y;
+ final long bx = b * x;
+ final long ay = a * y;
+ final long ax = a * x;
+
+ // Cannot overflow
+ final long carry = (by >>> 32) +
+ (bx & MASK32) +
+ ay;
+ // Note:
+ // low = (carry << 32) | (by & INT_TO_UNSIGNED_BYTE_MASK)
+ // Benchmarking shows outputting low to a long[] output argument
+ // has no benefit over computing 'low = value1 * value2' separately.
+
+ return (bx >>> 32) + (carry >>> 32) + ax;
+ }
+
+ /**
+ * Multiply the arguments as if unsigned integers to a {@code double} result.
+ *
+ * @param x Value.
+ * @param y Value.
+ * @return the double
+ */
+ static double unsignedMultiplyToDouble(long x, long y) {
+ final long lo = x * y;
+ // Fast case: check the arguments cannot overflow a long.
+ // This is true if neither has the upper 33-bits set.
+ if (((x | y) >>> 31) == 0) {
+ // Implicit conversion to a double.
+ return lo;
+ }
+ return uin128ToDouble(unsignedMultiplyHigh(x, y), lo);
+ }
+
+ /**
+ * Convert an unsigned 128-bit integer to a {@code double}.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ * @return the double
+ */
+ static double uin128ToDouble(long hi, long lo) {
+ // Require the representation:
+ // 2^exp * mantissa / 2^53
+ // The mantissa has an implied leading 1-bit.
+
+ // We have the mantissa final bit as xxx0 or xxx1.
+ // To perform correct rounding we maintain the 54-th bit (a) and
+ // a check bit (b) of remaining bits.
+ // Cases:
+ // xxx0 00 - round-down [1]
+ // xxx0 0b - round-down [1]
+ // xxx0 a0 - half-even, round-down [4]
+ // xxx0 ab - round-up [2]
+ // xxx1 00 - round-down [1]
+ // xxx1 0b - round-down [1]
+ // xxx1 a0 - half-even, round-up [3]
+ // xxx1 ab - round-up [2]
+ // [1] If the 54-th bit is 0 always round-down.
+ // [2] Otherwise round-up if the check bit is set or
+ // [3] the final bit is odd (half-even rounding up).
+ // [4] half-even rounding down.
+
+ if (hi == 0) {
+ // If lo is a 63-bit result then we are done
+ if (lo >= 0) {
+ return lo;
+ }
+ // Create a 63-bit number with a sticky bit for rounding, rescale the result
+ return 2 * (double) ((lo >>> 1) | (lo & 0x1));
+ }
+
+ // Initially we create the most significant 64-bits.
+ final int shift = Long.numberOfLeadingZeros(hi);
+ // Shift the high bits and add trailing low bits.
+ // The mask is for the bits from low that are *not* used.
+ // Flipping the mask obtains the bits we concatenate
+ // after shifting (64 - shift).
+ final long maskLow = -1L >>> shift;
+ long bits64 = (hi << shift) | ((lo & ~maskLow) >>> -shift);
+ // exponent for 2^exp is the index of the highest bit in the 128 bit integer
+ final int exp = 127 - shift;
+ // Some of the low bits are lost. If non-zero set
+ // a sticky bit for rounding.
+ bits64 |= (lo & maskLow) == 0 ? 0 : 1;
+
+ // We have a 64-bit unsigned fraction magnitude and an exponent.
+ // This must be converted to a IEEE double by mapping the fraction to a base of 2^53.
+
+ // Create the 53-bit mantissa without the implicit 1-bit
+ long bits = (bits64 >>> 11) & MASK52;
+ // Extract 54-th bit and a sticky bit
+ final long a = (bits64 >>> 10) & 0x1;
+ final long b = (bits64 << 54) == 0 ? 0 : 1;
+ // Perform half-even rounding.
+ bits += a & (b | (bits & 0x1));
+
+ // Add the exponent.
+ // No worry about overflow to the sign bit as the max exponent is 127.
+ bits += (long) (exp + EXP_BIAS) << EXP_SHIFT;
+
+ return Double.longBitsToDouble(bits);
+ }
+
+ /**
+ * Return the whole number that is nearest to the {@code double} argument {@code x}
+ * as an {@code int}, with ties rounding towards positive infinity.
+ *
+ * <p>This will raise an {@link ArithmeticException} if the closest
+ * integer result is not within the range {@code [-2^31, 2^31)},
+ * i.e. it overflows an {@code int}; or the argument {@code x}
+ * is not finite.
+ *
+ * <p>Note: This method is equivalent to:
+ * <pre>
+ * Math.toIntExact(Math.round(x))
+ * </pre>
+ *
+ * <p>The behaviour has been re-implemented for consistent error handling
+ * for {@code int}, {@code long} and {@code BigInteger} types.
+ *
+ * @param x Value.
+ * @return rounded value
+ * @throws ArithmeticException if the {@code result} overflows an {@code int},
+ * or {@code x} is not finite
+ * @see Math#round(double)
+ * @see Math#toIntExact(long)
+ */
+ static int toIntExact(double x) {
+ final double r = roundToInteger(x);
+ if (r >= -0x1.0p31 && r < 0x1.0p31) {
+ return (int) r;
+ }
+ throw new ArithmeticException("integer overflow: " + x);
+ }
+
+ /**
+ * Return the whole number that is nearest to the {@code double} argument {@code x}
+ * as an {@code long}, with ties rounding towards positive infinity.
+ *
+ * <p>This will raise an {@link ArithmeticException} if the closest
+ * integer result is not within the range {@code [-2^63, 2^63)},
+ * i.e. it overflows a {@code long}; or the argument {@code x}
+ * is not finite.
+ *
+ * @param x Value.
+ * @return rounded value
+ * @throws ArithmeticException if the {@code result} overflows a {@code long},
+ * or {@code x} is not finite
+ */
+ static long toLongExact(double x) {
+ final double r = roundToInteger(x);
+ if (r >= -0x1.0p63 && r < 0x1.0p63) {
+ return (long) r;
+ }
+ throw new ArithmeticException("long integer overflow: " + x);
+ }
+
+ /**
+ * Return the whole number that is nearest to the {@code double} argument {@code x}
+ * as an {@code int}, with ties rounding towards positive infinity.
+ *
+ * <p>This will raise an {@link ArithmeticException} if the argument {@code x}
+ * is not finite.
+ *
+ * @param x Value.
+ * @return rounded value
+ * @throws ArithmeticException if {@code x} is not finite
+ */
+ static BigInteger toBigIntegerExact(double x) {
+ if (!Double.isFinite(x)) {
+ throw new ArithmeticException("BigInteger overflow: " + x);
+ }
+ final double r = roundToInteger(x);
+ if (r >= -0x1.0p63 && r < 0x1.0p63) {
+ // Representable as a long
+ return BigInteger.valueOf((long) r);
+ }
+ // Large result
+ return new BigDecimal(r).toBigInteger();
+ }
+
+ /**
+ * Get the whole number that is the nearest to x, with ties rounding towards positive infinity.
+ *
+ * <p>This method is intended to perform the equivalent of
+ * {@link Math#round(double)} without converting to a {@code long} primitive type.
+ * This allows the domain of the result to be checked against the range {@code [-2^63, 2^63)}.
+ *
+ * <p>Note: Adapted from {@code o.a.c.math4.AccurateMath.rint} and
+ * modified to perform rounding towards positive infinity.
+ *
+ * @param x Number from which nearest whole number is requested.
+ * @return a double number r such that r is an integer {@code r - 0.5 <= x < r + 0.5}
+ */
+ private static double roundToInteger(double x) {
+ final double y = Math.floor(x);
+ final double d = x - y;
+ if (d >= HALF) {
+ // Here we do not preserve the sign of the operand in the case
+ // of -0.5 < x <= -0.0 since the rounded result is required as an integer.
+ // if y == -1.0:
+ // return -0.0
+ return y + 1.0;
+ }
+ return y;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMean.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMean.java
new file mode 100644
index 0000000..0713047
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntMean.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+/**
+ * Computes the arithmetic mean of the available values. Uses the following definition
+ * of the <em>sample mean</em>:
+ *
+ * <p>\[ \frac{1}{n} \sum_{i=1}^n x_i \]
+ *
+ * <p>where \( n \) is the number of samples.
+ *
+ * <ul>
+ * <li>The result is {@code NaN} if no values are added.
+ * </ul>
+ *
+ * <p>This class uses an exact integer sum to compute the mean. It supports up to 2<sup>63</sup>
+ * values as the count \( n \) is maintained as a {@code long}.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link java.util.function.IntConsumer#accept(int) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link java.util.function.IntConsumer#accept(int) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Mean">Mean (Wikipedia)</a>
+ * @since 1.1 */
+public final class IntMean implements IntStatistic, StatisticAccumulator<IntMean> {
+ /** Limit for small sample size where the sum can exactly map to a double.
+ * This is conservatively set using 2^21 values of 2^31 (2^21 ~ 2 million). */
+ private static final long SMALL_N = 1L << 21;
+
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /**
+ * Create an instance.
+ */
+ private IntMean() {
+ this(Int128.create(), 0);
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ private IntMean(Int128 sum, int n) {
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * <p>The initial result is {@code NaN}.
+ *
+ * @return {@code IntMean} instance.
+ */
+ public static IntMean create() {
+ return new IntMean();
+ }
+
+ /**
+ * Returns an instance populated using the input {@code values}.
+ *
+ * @param values Values.
+ * @return {@code IntMean} instance.
+ */
+ public static IntMean of(int... values) {
+ // Sum of an array cannot exceed a 64-bit long
+ long s = 0;
+ for (final int x : values) {
+ s += x;
+ }
+ // Convert
+ return new IntMean(Int128.of(s), values.length);
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code value}.
+ *
+ * @param value Value.
+ */
+ @Override
+ public void accept(int value) {
+ sum.add(value);
+ n++;
+ }
+
+ /**
+ * Gets the mean of all input values.
+ *
+ * <p>When no values have been added, the result is {@code NaN}.
+ *
+ * @return mean of all values.
+ */
+ @Override
+ public double getAsDouble() {
+ // Fast option when the sum fits within
+ // the mantissa of a double.
+ // Handles n=0 as NaN
+ if (n < SMALL_N) {
+ return (double) sum.lo64() / n;
+ }
+ // Extended precision.
+ // Could divide by DD.of(n) when |n| > 2^53.
+ return sum.toDD().divide(n).doubleValue();
+ }
+
+ @Override
+ public IntMean combine(IntMean other) {
+ sum.add(other.sum);
+ n += other.n;
+ return this;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntVariance.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntVariance.java
new file mode 100644
index 0000000..70d108b
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/IntVariance.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+
+/**
+ * Computes the variance of the available values. The default implementation uses the
+ * following definition of the <em>sample variance</em>:
+ *
+ * <p>\[ \tfrac{1}{n-1} \sum_{i=1}^n (x_i-\overline{x})^2 \]
+ *
+ * <p>where \( \overline{x} \) is the sample mean, and \( n \) is the number of samples.
+ *
+ * <ul>
+ * <li>The result is {@code NaN} if no values are added.
+ * <li>The result is zero if there is one value in the data set.
+ * </ul>
+ *
+ * <p>The use of the term \( n − 1 \) is called Bessel's correction. This is an unbiased
+ * estimator of the variance of a hypothetical infinite population. If the
+ * {@link #setBiased(boolean) biased} option is enabled the normalisation factor is
+ * changed to \( \frac{1}{n} \) for a biased estimator of the <em>sample variance</em>.
+ *
+ * <p>The implementation uses an exact integer sum to compute the scaled (by \( n \))
+ * sum of squared deviations from the mean; this is normalised by the scaled correction factor.
+ *
+ * <p>\[ \frac {n \times \sum_{i=1}^n x_i^2 - (\sum_{i=1}^n x_i)^2}{n \times (n - 1)} \]
+ *
+ * <p>It supports up to 2<sup>63</sup> values as the count \( n \) is maintained
+ * as a {@code long}.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link java.util.function.IntConsumer#accept(int) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link java.util.function.IntConsumer#accept(int) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/variance">variance (Wikipedia)</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance">
+ * Algorithms for computing the variance (Wikipedia)</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Bessel%27s_correction">Bessel's correction</a>
+ * @since 1.1
+ */
+public final class IntVariance implements IntStatistic, StatisticAccumulator<IntVariance> {
+ /** Small array sample size.
+ * Used to avoid computing with UInt96 then converting to UInt128. */
+ private static final int SMALL_SAMPLE = 10;
+
+ /** Sum of the squared values. */
+ private final UInt128 sumSq;
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /** Flag to control if the statistic is biased, or should use a bias correction. */
+ private boolean biased;
+
+ /**
+ * Create an instance.
+ */
+ private IntVariance() {
+ this(UInt128.create(), Int128.create(), 0);
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param sumSq Sum of the squared values.
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ private IntVariance(UInt128 sumSq, Int128 sum, int n) {
+ this.sumSq = sumSq;
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * <p>The initial result is {@code NaN}.
+ *
+ * @return {@code IntVariance} instance.
+ */
+ public static IntVariance create() {
+ return new IntVariance();
+ }
+
+ /**
+ * Returns an instance populated using the input {@code values}.
+ *
+ * @param values Values.
+ * @return {@code IntVariance} instance.
+ */
+ public static IntVariance of(int... values) {
+ // Small arrays can be processed using the object
+ if (values.length < SMALL_SAMPLE) {
+ final IntVariance stat = new IntVariance();
+ for (final int x : values) {
+ stat.accept(x);
+ }
+ return stat;
+ }
+
+ // Arrays can be processed using specialised counts knowing the maximum limit
+ // for an array is 2^31 values.
+ long s = 0;
+ final UInt96 ss = UInt96.create();
+ // Process pairs as we know two maximum value int^2 will not overflow
+ // an unsigned long.
+ final int end = values.length & ~0x1;
+ for (int i = 0; i < end; i += 2) {
+ final long x = values[i];
+ final long y = values[i + 1];
+ s += x + y;
+ ss.addPositive(x * x + y * y);
+ }
+ if (end < values.length) {
+ final long x = values[end];
+ s += x;
+ ss.addPositive(x * x);
+ }
+
+ // Convert
+ return new IntVariance(UInt128.of(ss), Int128.of(s), values.length);
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code value}.
+ *
+ * @param value Value.
+ */
+ @Override
+ public void accept(int value) {
+ sumSq.addPositive((long) value * value);
+ sum.add(value);
+ n++;
+ }
+
+ /**
+ * Gets the variance of all input values.
+ *
+ * <p>When no values have been added, the result is {@code NaN}.
+ *
+ * @return variance of all values.
+ */
+ @Override
+ public double getAsDouble() {
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final long n0 = biased ? n : n - 1;
+
+ // Sum-of-squared deviations: sum(x^2) - sum(x)^2 / n
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // The precursor is computed in integer precision.
+ // The divide uses double precision.
+ // This ensures we avoid cancellation in the difference and use a fast divide.
+ // The result is limited to by the rounding in the double computation.
+
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ double diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDouble();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(square(sum.toBigInteger())).doubleValue();
+ }
+ // Compute the divide in double precision
+ return diff / IntMath.unsignedMultiplyToDouble(n, n0);
+ }
+
+ /**
+ * Convenience method to square a BigInteger.
+ *
+ * @param x Value
+ * @return x^2
+ */
+ private static BigInteger square(BigInteger x) {
+ return x.multiply(x);
+ }
+
+ @Override
+ public IntVariance combine(IntVariance other) {
+ sumSq.add(other.sumSq);
+ sum.add(other.sum);
+ n += other.n;
+ return this;
+ }
+
+ /**
+ * Sets the value of the biased flag. The default value is {@code false}.
+ *
+ * <p>If {@code false} the sum of squared deviations from the sample mean is normalised by
+ * {@code n - 1} where {@code n} is the number of samples. This is Bessel's correction
+ * for an unbiased estimator of the variance of a hypothetical infinite population.
+ *
+ * <p>If {@code true} the sum of squared deviations is normalised by the number of samples
+ * {@code n}.
+ *
+ * <p>Note: This option only applies when {@code n > 1}. The variance of {@code n = 1} is
+ * always 0.
+ *
+ * <p>This flag only controls the final computation of the statistic. The value of this flag
+ * will not affect compatibility between instances during a {@link #combine(IntVariance) combine}
+ * operation.
+ *
+ * @param v Value.
+ * @return {@code this} instance
+ */
+ public IntVariance setBiased(boolean v) {
+ biased = v;
+ return this;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongMean.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongMean.java
new file mode 100644
index 0000000..3f84a9e
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongMean.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+/**
+ * Computes the arithmetic mean of the available values. Uses the following definition
+ * of the <em>sample mean</em>:
+ *
+ * <p>\[ \frac{1}{n} \sum_{i=1}^n x_i \]
+ *
+ * <p>where \( n \) is the number of samples.
+ *
+ * <ul>
+ * <li>The result is {@code NaN} if no values are added.
+ * </ul>
+ *
+ * <p>This class uses an exact integer sum to compute the mean. It supports up to 2<sup>63</sup>
+ * values as the count \( n \) is maintained as a {@code long}.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link java.util.function.LongConsumer#accept(long) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link java.util.function.LongConsumer#accept(long) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @since 1.1
+ */
+public final class LongMean implements LongStatistic, StatisticAccumulator<LongMean> {
+ /** Limit where the absolute sum can exactly map to a double. Set to 2^53. */
+ private static final long SMALL_SUM = 1L << 53;
+
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /**
+ * Create an instance.
+ */
+ private LongMean() {
+ this(Int128.create(), 0);
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ private LongMean(Int128 sum, int n) {
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * <p>The initial result is {@code NaN}.
+ *
+ * @return {@code IntMean} instance.
+ */
+ public static LongMean create() {
+ return new LongMean();
+ }
+
+ /**
+ * Returns an instance populated using the input {@code values}.
+ *
+ * @param values Values.
+ * @return {@code IntMean} instance.
+ */
+ public static LongMean of(long... values) {
+ final Int128 s = Int128.create();
+ for (final long x : values) {
+ s.add(x);
+ }
+ return new LongMean(s, values.length);
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code value}.
+ *
+ * @param value Value.
+ */
+ @Override
+ public void accept(long value) {
+ sum.add(value);
+ n++;
+ }
+
+ /**
+ * Gets the mean of all input values.
+ *
+ * <p>When no values have been added, the result is {@code NaN}.
+ *
+ * @return mean of all values.
+ */
+ @Override
+ public double getAsDouble() {
+ // Fast option when the sum fits within
+ // the mantissa of a double.
+ // Handles n=0 as NaN
+ if (sum.hi64() == 0 && Math.abs(sum.lo64()) < SMALL_SUM) {
+ return (double) sum.lo64() / n;
+ }
+ // Extended precision.
+ // Could divide by DD.of(n) when |n| > 2^53.
+ return sum.toDD().divide(n).doubleValue();
+ }
+
+ @Override
+ public LongMean combine(LongMean other) {
+ sum.add(other.sum);
+ n += other.n;
+ return this;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongVariance.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongVariance.java
new file mode 100644
index 0000000..a6cbe72
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/LongVariance.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+
+/**
+ * Computes the variance of the available values. The default implementation uses the
+ * following definition of the <em>sample variance</em>:
+ *
+ * <p>\[ \tfrac{1}{n-1} \sum_{i=1}^n (x_i-\overline{x})^2 \]
+ *
+ * <p>where \( \overline{x} \) is the sample mean, and \( n \) is the number of samples.
+ *
+ * <ul>
+ * <li>The result is {@code NaN} if no values are added.
+ * <li>The result is zero if there is one value in the data set.
+ * </ul>
+ *
+ * <p>The use of the term \( n − 1 \) is called Bessel's correction. This is an unbiased
+ * estimator of the variance of a hypothetical infinite population. If the
+ * {@link #setBiased(boolean) biased} option is enabled the normalisation factor is
+ * changed to \( \frac{1}{n} \) for a biased estimator of the <em>sample variance</em>.
+ *
+ * <p>The implementation uses an exact integer sum to compute the scaled (by \( n \))
+ * sum of squared deviations from the mean; this is normalised by the scaled correction factor.
+ *
+ * <p>\[ \frac {n \times \sum_{i=1}^n x_i^2 - (\sum_{i=1}^n x_i)^2}{n \times (n - 1)} \]
+ *
+ * <p>It supports up to 2<sup>63</sup> values as the count \( n \) is maintained
+ * as a {@code long}.
+ *
+ * <p>This class is designed to work with (though does not require)
+ * {@linkplain java.util.stream streams}.
+ *
+ * <p><strong>This implementation is not thread safe.</strong>
+ * If multiple threads access an instance of this class concurrently,
+ * and at least one of the threads invokes the {@link java.util.function.LongConsumer#accept(long) accept} or
+ * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally.
+ *
+ * <p>However, it is safe to use {@link java.util.function.LongConsumer#accept(long) accept}
+ * and {@link StatisticAccumulator#combine(StatisticResult) combine}
+ * as {@code accumulator} and {@code combiner} functions of
+ * {@link java.util.stream.Collector Collector} on a parallel stream,
+ * because the parallel implementation of {@link java.util.stream.Stream#collect Stream.collect()}
+ * provides the necessary partitioning, isolation, and merging of results for
+ * safe and efficient parallel execution.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/variance">variance (Wikipedia)</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance">
+ * Algorithms for computing the variance (Wikipedia)</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Bessel%27s_correction">Bessel's correction</a>
+ * @since 1.1
+ */
+public final class LongVariance implements LongStatistic, StatisticAccumulator<LongVariance> {
+
+ /** Sum of the squared values. */
+ private final UInt192 sumSq;
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /** Flag to control if the statistic is biased, or should use a bias correction. */
+ private boolean biased;
+
+ /**
+ * Create an instance.
+ */
+ private LongVariance() {
+ this(UInt192.create(), Int128.create(), 0);
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param sumSq Sum of the squared values.
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ private LongVariance(UInt192 sumSq, Int128 sum, int n) {
+ this.sumSq = sumSq;
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * <p>The initial result is {@code NaN}.
+ *
+ * @return {@code IntVariance} instance.
+ */
+ public static LongVariance create() {
+ return new LongVariance();
+ }
+
+ /**
+ * Returns an instance populated using the input {@code values}.
+ *
+ * @param values Values.
+ * @return {@code IntVariance} instance.
+ */
+ public static LongVariance of(long... values) {
+ // Note: Arrays could be processed using specialised counts knowing the maximum limit
+ // for an array is 2^31 values. Requires a UInt160.
+
+ final Int128 s = Int128.create();
+ final UInt192 ss = UInt192.create();
+ for (final long x : values) {
+ s.add(x);
+ ss.addSquare(x);
+ }
+ return new LongVariance(ss, s, values.length);
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code value}.
+ *
+ * @param value Value.
+ */
+ @Override
+ public void accept(long value) {
+ sumSq.addSquare(value);
+ sum.add(value);
+ n++;
+ }
+
+ /**
+ * Gets the variance of all input values.
+ *
+ * <p>When no values have been added, the result is {@code NaN}.
+ *
+ * @return variance of all values.
+ */
+ @Override
+ public double getAsDouble() {
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final long n0 = biased ? n : n - 1;
+
+ // Sum-of-squared deviations: sum(x^2) - sum(x)^2 / n
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // The precursor is computed in integer precision.
+ // The divide uses double precision.
+ // This ensures we avoid cancellation in the difference and use a fast divide.
+ // The result is limited to max 4 ulp by the rounding in the double computation
+ // When n0*n is < 2^53 the max error is reduced to two roundings.
+
+ // Compute the term if possible using fast integer arithmetic.
+ // 192-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // The first is safe when n < 2^32 but we must check the sum high bits.
+ double diff;
+ if (((n >>> Integer.SIZE) | sum.hi64()) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDouble();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(square(sum.toBigInteger())).doubleValue();
+ }
+ // Compute the divide in double precision
+ return diff / IntMath.unsignedMultiplyToDouble(n, n0);
+ }
+
+ /**
+ * Convenience method to square a BigInteger.
+ *
+ * @param x Value
+ * @return x^2
+ */
+ private static BigInteger square(BigInteger x) {
+ return x.multiply(x);
+ }
+
+ @Override
+ public LongVariance combine(LongVariance other) {
+ sumSq.add(other.sumSq);
+ sum.add(other.sum);
+ n += other.n;
+ return this;
+ }
+
+ /**
+ * Sets the value of the biased flag. The default value is {@code false}.
+ *
+ * <p>If {@code false} the sum of squared deviations from the sample mean is normalised by
+ * {@code n - 1} where {@code n} is the number of samples. This is Bessel's correction
+ * for an unbiased estimator of the variance of a hypothetical infinite population.
+ *
+ * <p>If {@code true} the sum of squared deviations is normalised by the number of samples
+ * {@code n}.
+ *
+ * <p>Note: This option only applies when {@code n > 1}. The variance of {@code n = 1} is
+ * always 0.
+ *
+ * <p>This flag only controls the final computation of the statistic. The value of this flag
+ * will not affect compatibility between instances during a {@link #combine(LongVariance) combine}
+ * operation.
+ *
+ * @param v Value.
+ * @return {@code this} instance
+ */
+ public LongVariance setBiased(boolean v) {
+ biased = v;
+ return this;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
new file mode 100644
index 0000000..c4b421c
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt128.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/**
+ * A mutable 128-bit unsigned integer.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values
+ * generated by squaring {@code int} values.
+ *
+ * @since 1.1
+ */
+final class UInt128 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Data is stored using integers to allow efficient sum-with-carry addition
+
+ /** bits 32-1 (low 32-bits). */
+ private int d;
+ /** bits 64-33. */
+ private int c;
+ /** bits 128-65. */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt128() {
+ // No-op
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ *
+ * @param hi High 64-bits.
+ * @param mid Middle 32-bits.
+ * @param lo Low 32-bits.
+ */
+ private UInt128(long hi, int mid, int lo) {
+ this.d = lo;
+ this.c = mid;
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ */
+ UInt128(long hi, long lo) {
+ this.d = (int) lo;
+ this.c = (int) (lo >>> Integer.SIZE);
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt128 create() {
+ return new UInt128();
+ }
+
+ /**
+ * Create an instance of the {@code UInt96} value.
+ *
+ * @param x Value.
+ * @return the instance
+ */
+ static UInt128 of(UInt96 x) {
+ final int lo = x.lo32();
+ final long hi = x.hi64();
+ final UInt128 y = new UInt128();
+ y.d = lo;
+ y.c = (int) hi;
+ y.ab = hi >>> Integer.SIZE;
+ return y;
+ }
+
+ /**
+ * Adds the value in place. It is assumed to be positive, for example the square of an
+ * {@code int} value. However no check is performed for a negative value.
+ *
+ * <p>Note: This addition handles {@value Long#MIN_VALUE} as an unsigned
+ * value of 2^63.
+ *
+ * @param x Value.
+ */
+ void addPositive(long x) {
+ // Sum with carry.
+ // Assuming x is positive then x + lo will not overflow 64-bits
+ // so we do not have to split x into upper and lower 32-bit values.
+ long s = x + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value in-place.
+ *
+ * @param x Value.
+ */
+ void add(UInt128 x) {
+ // Avoid issues adding to itself
+ final int dd = x.d;
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ long s = (dd & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+ /**
+ * Multiply by the unsigned value.
+ * Any overflow bits are lost.
+ *
+ * @param x Value.
+ * @return the product
+ */
+ UInt128 unsignedMultiply(int x) {
+ final long xx = x & MASK32;
+ // Multiply with carry.
+ long product = xx * (d & MASK32);
+ final int dd = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (c & MASK32);
+ final int cc = (int) product;
+ // Possible overflow here and bits are lost
+ final long aabb = (product >>> Integer.SIZE) + xx * ab;
+ return new UInt128(aabb, cc, dd);
+ }
+
+ /**
+ * Subtracts the value.
+ * Any overflow bits (negative result) are lost.
+ *
+ * @param x Value.
+ * @return the difference
+ */
+ UInt128 subtract(UInt128 x) {
+ // Difference with carry.
+ long diff = (d & MASK32) - (x.d & MASK32);
+ final int dd = (int) diff;
+ diff = (diff >> Integer.SIZE) + (c & MASK32) - (x.c & MASK32);
+ final int cc = (int) diff;
+ // Possible overflow here and bits are lost containing info on the
+ // magnitude of the true negative value
+ final long aabb = (diff >> Integer.SIZE) + ab - x.ab;
+ return new UInt128(aabb, cc, dd);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ // Test if we have more than 63-bits
+ if (ab != 0 || c < 0) {
+ return new BigInteger(1, ByteBuffer.allocate(Integer.BYTES * 4)
+ .putLong(ab)
+ .putInt(c)
+ .putInt(d).array());
+ }
+ // Create from a long
+ return BigInteger.valueOf(lo64());
+ }
+
+ /**
+ * Convert to a {@code double}.
+ *
+ * @return the value
+ */
+ double toDouble() {
+ return IntMath.uin128ToDouble(hi64(), lo64());
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * @return bits 64-1
+ */
+ long lo64() {
+ return (d & MASK32) | ((c & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the low 32-bits as an {@code int} value.
+ *
+ * @return bits 32-1
+ */
+ int lo32() {
+ return d;
+ }
+
+ /**
+ * Return the middle 32-bits as an {@code int} value.
+ *
+ * @return bits 64-33
+ */
+ int mid32() {
+ return c;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 128-65
+ */
+ long hi64() {
+ return ab;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
new file mode 100644
index 0000000..f390944
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt192.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/**
+ * A mutable 192-bit unsigned integer.
+ *
+ * <p>This is a specialised class to implement an accumulator of squared {@code long} values.
+ *
+ * @since 1.1
+ */
+final class UInt192 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Data is stored using integers to allow efficient sum-with-carry addition
+
+ /** bits 32-1 (low 32-bits). */
+ private int f;
+ /** bits 64-33. */
+ private int e;
+ /** bits 96-65. */
+ private int d;
+ /** bits 128-97. */
+ private int c;
+ /** bits 192-129 (high 64-bits). */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt192() {
+ // No-op
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param mid Middle 64-bits.
+ * @param lo Low 64-bits.
+ */
+ UInt192(long hi, long mid, long lo) {
+ this.f = (int) lo;
+ this.e = (int) (lo >>> Integer.SIZE);
+ this.d = (int) mid;
+ this.c = (int) (mid >>> Integer.SIZE);
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ *
+ * @param ab bits 192-129 (high 64-bits).
+ * @param c bits 128-97.
+ * @param d bits 96-65.
+ * @param e bits 64-33.
+ * @param f bits 32-1.
+ */
+ private UInt192(long ab, int c, int d, int e, int f) {
+ this.ab = ab;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt192 create() {
+ return new UInt192();
+ }
+
+ /**
+ * Adds the squared value {@code x * x}.
+ *
+ * @param x Value.
+ */
+ void addSquare(long x) {
+ final long lo = x * x;
+ final long hi = IntMath.squareHigh(x);
+
+ // Sum with carry.
+ long s = (lo & MASK32) + (f & MASK32);
+ f = (int) s;
+ s = (s >>> Integer.SIZE) + (lo >>> Integer.SIZE) + (e & MASK32);
+ e = (int) s;
+ s = (s >>> Integer.SIZE) + (hi & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (hi >>> Integer.SIZE) + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(UInt192 x) {
+ // Avoid issues adding to itself
+ final int ff = x.f;
+ final int ee = x.e;
+ final int dd = x.d;
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ long s = (ff & MASK32) + (f & MASK32);
+ f = (int) s;
+ s = (s >>> Integer.SIZE) + (ee & MASK32) + (e & MASK32);
+ e = (int) s;
+ s = (s >>> Integer.SIZE) + (dd & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+
+ /**
+ * Multiply by the unsigned value.
+ * Any overflow bits are lost.
+ *
+ * @param x Value.
+ * @return the product
+ */
+ UInt192 unsignedMultiply(int x) {
+ final long xx = x & MASK32;
+ // Multiply with carry.
+ long product = xx * (f & MASK32);
+ final int ff = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (e & MASK32);
+ final int ee = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (d & MASK32);
+ final int dd = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (c & MASK32);
+ final int cc = (int) product;
+ // Possible overflow here and bits are lost
+ final long aabb = (product >>> Integer.SIZE) + xx * ab;
+ return new UInt192(aabb, cc, dd, ee, ff);
+ }
+
+ /**
+ * Subtracts the value.
+ * Any overflow bits (negative result) are lost.
+ *
+ * @param x Value.
+ * @return the difference
+ */
+ UInt192 subtract(UInt128 x) {
+ // Difference with carry.
+ // Subtract common part.
+ long diff = (f & MASK32) - (x.lo32() & MASK32);
+ final int ff = (int) diff;
+ diff = (diff >> Integer.SIZE) + (e & MASK32) - (x.mid32() & MASK32);
+ final int ee = (int) diff;
+ diff = (diff >> Integer.SIZE) + (d & MASK32) - (x.hi64() & MASK32);
+ final int dd = (int) diff;
+ diff = (diff >> Integer.SIZE) + (c & MASK32) - (x.hi64() >>> Integer.SIZE);
+ final int cc = (int) diff;
+ // Possible overflow here and bits are lost containing info on the
+ // magnitude of the true negative value
+ final long aabb = (diff >> Integer.SIZE) + ab;
+ return new UInt192(aabb, cc, dd, ee, ff);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ final ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES * 6)
+ .putLong(ab)
+ .putInt(c)
+ .putInt(d)
+ .putInt(e)
+ .putInt(f);
+ // Sign is always positive. This works for zero.
+ return new BigInteger(1, bb.array());
+ }
+
+ /**
+ * Convert to a double.
+ *
+ * @return the value
+ */
+ double toDouble() {
+ final long h = hi64();
+ final long m = mid64();
+ final long l = lo64();
+ if (h == 0) {
+ return IntMath.uin128ToDouble(m, l);
+ }
+ // For correct rounding we use a sticky bit to represent magnitude
+ // lost from the low 64-bits. The result is scaled by 2^64.
+ return IntMath.uin128ToDouble(h, m | ((l == 0) ? 0 : 1)) * 0x1.0p64;
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * @return the low 64-bits
+ */
+ long lo64() {
+ return (f & MASK32) | ((e & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the middle 64-bits as a {@code long} value.
+ *
+ * @return bits 128-65
+ */
+ long mid64() {
+ return (d & MASK32) | ((c & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 192-129
+ */
+ long hi64() {
+ return ab;
+ }
+}
diff --git a/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt96.java b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt96.java
new file mode 100644
index 0000000..856a313
--- /dev/null
+++ b/commons-statistics-descriptive/src/main/java/org/apache/commons/statistics/descriptive/UInt96.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/**
+ * A mutable 96-bit unsigned integer.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values
+ * generated by squaring {@code int} values from an array (max observations=2^31).
+ *
+ * @since 1.1
+ */
+final class UInt96 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Low data is stored using an integer to allow efficient sum-with-carry addition
+
+ /** bits 32-1 (low 32-bits). */
+ private int c;
+ /** bits 96-33. */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt96() {
+ // No-op
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param x Value.
+ */
+ private UInt96(long x) {
+ c = (int) x;
+ ab = (int) (x >>> Integer.SIZE);
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 32-bits.
+ */
+ UInt96(long hi, int lo) {
+ this.c = lo;
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt96 create() {
+ return new UInt96();
+ }
+
+ /**
+ * Create an instance of the {@code long} value.
+ * The value is assumed to be an unsigned 64-bit integer.
+ *
+ * @param x Value (must be positive).
+ * @return the instance
+ */
+ static UInt96 of(long x) {
+ return new UInt96(x);
+ }
+
+ /**
+ * Adds the value. It is assumed to be positive, for example the square of an
+ * {@code int} value. However no check is performed for a negative value.
+ *
+ * <p>Note: This addition handles {@value Long#MIN_VALUE} as an unsigned
+ * value of 2^63.
+ *
+ * @param x Value.
+ */
+ void addPositive(long x) {
+ // Sum with carry.
+ // Assuming x is positive then x + lo will not overflow 64-bits
+ // so we do not have to split x into upper and lower 32-bit values.
+ final long s = x + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(UInt96 x) {
+ // Avoid issues adding to itself
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ final long s = (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ if (ab != 0) {
+ final ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES * 3)
+ .putLong(ab)
+ .putInt(c);
+ return new BigInteger(1, bb.array());
+ }
+ return BigInteger.valueOf(c & MASK32);
+ }
+
+ /**
+ * Return the lower 32-bits as an {@code int} value.
+ *
+ * @return bits 32-1
+ */
+ int lo32() {
+ return c;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 96-33
+ */
+ long hi64() {
+ return ab;
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseIntStatisticTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseIntStatisticTest.java
index 7b05937..4460121 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseIntStatisticTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseIntStatisticTest.java
@@ -29,6 +29,7 @@ import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.statistics.distribution.DoubleTolerance;
import org.apache.commons.statistics.distribution.TestUtils;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@@ -395,6 +396,8 @@ abstract class BaseIntStatisticTest<S extends IntStatistic & StatisticAccumulato
* Creates the equivalent {@link DoubleStatistic} from the {@code values}.
* This is used to cross-validate the {@link IntStatistic} result.
*
+ * <p>The test will be skipped if this method returns {@code null}.
+ *
* @param values Values.
* @return the statistic
*/
@@ -876,7 +879,9 @@ abstract class BaseIntStatisticTest<S extends IntStatistic & StatisticAccumulato
@ParameterizedTest
@MethodSource(value = {"testAccept"})
final void testVsDoubleStatistic(int[] values) {
- final double expected = createAsDoubleStatistic(values).getAsDouble();
+ final DoubleStatistic stat = createAsDoubleStatistic(values);
+ Assumptions.assumeTrue(stat != null);
+ final double expected = stat.getAsDouble();
final DoubleTolerance tol = getToleranceAsDouble();
TestUtils.assertEquals(expected, Statistics.add(create(), values).getAsDouble(), tol,
() -> statisticName + " accept: " + format(values));
@@ -1212,17 +1217,4 @@ abstract class BaseIntStatisticTest<S extends IntStatistic & StatisticAccumulato
.map(BaseIntStatisticTest::format)
.collect(Collectors.joining(", "));
}
-
- /**
- * Re-throw the error wrapped in an AssertionError with a message that appends the seed
- * and repeat for the random order test.
- *
- * @param e Error.
- * @param seed Seed.
- * @param repeat Repeat of the total random permutations.
- */
- private static void rethrowWithSeedAndRepeat(AssertionError e, long[] seed, int repeat) {
- throw new AssertionError(String.format("%s; Seed=%s; Repeat=%d/%d",
- e.getMessage(), Arrays.toString(seed), repeat, RANDOM_PERMUTATIONS), e);
- }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseLongStatisticTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseLongStatisticTest.java
index c3a567d..f8015de 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseLongStatisticTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseLongStatisticTest.java
@@ -29,6 +29,7 @@ import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.statistics.distribution.DoubleTolerance;
import org.apache.commons.statistics.distribution.TestUtils;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@@ -67,7 +68,7 @@ import org.junit.jupiter.params.provider.MethodSource;
* This is used to verify that test cases are added using the correct type and
* test tolerances are configured for {@code double} result types.
* <li>{@link #create()}: Create an empty statistic.
- * <li>{@link #create(int...)}: Create a statistic from a set of values.
+ * <li>{@link #create(long...)}: Create a statistic from a set of values.
* <li>{@link #getEmptyValue()}: The expected value of a statistic when not enough values have
* been observed. The minimum number of values can be provided in {@link #getEmptySize()}.
* <li>{@link #getExpectedValue(long[])}: A method to compute an expected value for the
@@ -97,7 +98,7 @@ import org.junit.jupiter.params.provider.MethodSource;
* The default uses the class name without the "Test" suffix.
* <li>{@link #isCombineSymmetric()}: Specify whether to test {@code a.combine(b)} is exactly
* equal to {@code b.combine(a)}. The default is {@code true}.
- * <li>{@link #mapValue(int)}: A method to update the sample data to the valid domain
+ * <li>{@link #mapValue(long)}: A method to update the sample data to the valid domain
* for the statistic. This can be used to alter the default test data, for example
* by mapping any negative values to positive.
* </ul>
@@ -395,6 +396,8 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
* Creates the equivalent {@link DoubleStatistic} from the {@code values}.
* This is used to cross-validate the {@link LongStatistic} result.
*
+ * <p>The test will be skipped if this method returns {@code null}.
+ *
* @param values Values.
* @return the statistic
*/
@@ -606,8 +609,8 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
* <li>{@link java.util.function.LongConsumer#accept accept}
* <li>{@link java.util.function.LongConsumer#accept accept} and
* {@link StatisticAccumulator#combine(StatisticResult) combine}
- * <li>{@link #create(int...)}
- * <li>{@link #create(int...)} and
+ * <li>{@link #create(long...)}
+ * <li>{@link #create(long...)} and
* {@link StatisticAccumulator#combine(StatisticResult) combine}
* </ol>
*
@@ -649,7 +652,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Stream the arguments to test the computation of the statistic using the
- * {@link #create(int...)} method. The expected value and tolerance are supplied
+ * {@link #create(long...)} method. The expected value and tolerance are supplied
* by the implementing class.
*
* @return the stream
@@ -666,7 +669,9 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
@ParameterizedTest
@MethodSource(value = {"testAccept"})
final void testVsDoubleStatistic(long[] values) {
- final double expected = createAsDoubleStatistic(values).getAsDouble();
+ final DoubleStatistic stat = createAsDoubleStatistic(values);
+ Assumptions.assumeTrue(stat != null);
+ final double expected = stat.getAsDouble();
final DoubleTolerance tol = getToleranceAsDouble();
TestUtils.assertEquals(expected, Statistics.add(create(), values).getAsDouble(), tol,
() -> statisticName + " accept: " + format(values));
@@ -693,7 +698,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Stream the arguments to test the computation of the statistic using the
- * {@link java.util.function.LongConsumer#accept(int) accept} method for each
+ * {@link java.util.function.LongConsumer#accept(long) accept} method for each
* array, then the {@link StatisticAccumulator#combine(StatisticResult) combine}
* method. The expected value and tolerance are supplied by the implementing class.
*
@@ -705,7 +710,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Stream the arguments to test the computation of the statistic using the
- * {@link #create(int...)} method for each array, then the
+ * {@link #create(long...)} method for each array, then the
* {@link StatisticAccumulator#combine(StatisticResult) combine} method. The
* expected value and tolerance are supplied by the implementing class.
*
@@ -717,7 +722,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Stream the arguments to test the computation of the statistic using the
- * {@link java.util.function.LongConsumer#accept(int) accept} method for each
+ * {@link java.util.function.LongConsumer#accept(long) accept} method for each
* element of a parallel stream, then the
* {@link StatisticAccumulator#combine(StatisticResult) combine} method.
* The expected value and tolerance are supplied by the implementing class.
@@ -863,9 +868,9 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Test the computation of the statistic using the
- * {@link java.util.function.LongConsumer#accept(int) accept} method. The
+ * {@link java.util.function.LongConsumer#accept(long) accept} method. The
* statistic is created using both the {@link #create()} and the
- * {@link #create(int...)} methods; the two instances must compute the same result.
+ * {@link #create(long...)} methods; the two instances must compute the same result.
*/
@ParameterizedTest
@MethodSource
@@ -876,7 +881,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
}
/**
- * Test the computation of the statistic using the {@link #create(int...)} method.
+ * Test the computation of the statistic using the {@link #create(long...)} method.
*/
@ParameterizedTest
@MethodSource
@@ -886,7 +891,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Test the computation of the statistic using the
- * {@link java.util.function.LongConsumer#accept(int) accept} method for each
+ * {@link java.util.function.LongConsumer#accept(long) accept} method for each
* array, then the {@link StatisticAccumulator#combine(StatisticResult) combine}
* method.
*/
@@ -898,7 +903,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Test the computation of the statistic using the
- * {@link java.util.function.LongConsumer#accept(int) accept} method for each
+ * {@link java.util.function.LongConsumer#accept(long) accept} method for each
* array, then the {@link StatisticAccumulator#combine(StatisticResult) combine}
* method.
*/
@@ -941,7 +946,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Test the computation of the statistic using a parallel stream of {@code double}
* values. The accumulator is the
- * {@link java.util.function.LongConsumer#accept(int) accept} method; the
+ * {@link java.util.function.LongConsumer#accept(long) accept} method; the
* combiner is the {@link StatisticAccumulator#combine(StatisticResult) combine}
* method.
*
@@ -964,7 +969,7 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
/**
* Test the computation of the statistic using a parallel stream of {@code long[]}
- * arrays. The arrays are mapped to a statistic using the {@link #create(int...)}
+ * arrays. The arrays are mapped to a statistic using the {@link #create(long...)}
* method, and the stream reduced using the
* {@link StatisticAccumulator#combine(StatisticResult) combine} method.
*
@@ -1212,17 +1217,4 @@ abstract class BaseLongStatisticTest<S extends LongStatistic & StatisticAccumula
.map(BaseLongStatisticTest::format)
.collect(Collectors.joining(", "));
}
-
- /**
- * Re-throw the error wrapped in an AssertionError with a message that appends the seed
- * and repeat for the random order test.
- *
- * @param e Error.
- * @param seed Seed.
- * @param repeat Repeat of the total random permutations.
- */
- private static void rethrowWithSeedAndRepeat(AssertionError e, long[] seed, int repeat) {
- throw new AssertionError(String.format("%s; Seed=%s; Repeat=%d/%d",
- e.getMessage(), Arrays.toString(seed), repeat, RANDOM_PERMUTATIONS), e);
- }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseStatisticTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseStatisticTest.java
index f058c84..ef28f72 100644
--- a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseStatisticTest.java
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/BaseStatisticTest.java
@@ -17,6 +17,7 @@
package org.apache.commons.statistics.descriptive;
import java.math.BigInteger;
+import java.util.Arrays;
import org.apache.commons.statistics.distribution.DoubleTolerance;
import org.apache.commons.statistics.distribution.DoubleTolerances;
import org.junit.jupiter.api.Assertions;
@@ -81,7 +82,7 @@ abstract class BaseStatisticTest {
protected abstract StatisticResult getEmptyValue();
/**
- * Creates the statistic result using an {@code double} value.
+ * Creates the statistic result using a {@code double} value.
*
* @param value Value.
* @return the statistic result
@@ -148,7 +149,7 @@ abstract class BaseStatisticTest {
/**
* Gets the tolerance for equality of the statistic and the expected value
- * for the {@link #testAccept(int[], StatisticResult, DoubleTolerance)} test.
+ * for a test using the primitive consumer {@code accept} method.
*
* <p>The default implementation uses {@link #getTolerance()}.
*
@@ -160,7 +161,7 @@ abstract class BaseStatisticTest {
/**
* Gets the tolerance for equality of the statistic and the expected value
- * for the {@link #testArray(int[], StatisticResult, DoubleTolerance)} test.
+ * for a test using creation from a primitive array.
*
* <p>The default implementation uses {@link #getTolerance()}.
*
@@ -172,7 +173,8 @@ abstract class BaseStatisticTest {
/**
* Gets the tolerance for equality of the statistic and the expected value
- * for the {@link #testAcceptAndCombine(int[][], StatisticResult, DoubleTolerance)} test.
+ * for a test using the primitive consumer {@code accept} method to create instances
+ * that are combined using {@link StatisticAccumulator#combine(StatisticResult)}.
*
* <p>The default implementation uses {@link #getTolerance()}.
*
@@ -184,7 +186,8 @@ abstract class BaseStatisticTest {
/**
* Gets the tolerance for equality of the statistic and the expected value
- * for the {@link #testArrayAndCombine(int[][], StatisticResult, DoubleTolerance)} test.
+ * for a test using creation from a primitive array to create instances
+ * that are combined using {@link StatisticAccumulator#combine(StatisticResult)}.
*
* <p>The default implementation uses {@link #getTolerance()}.
*
@@ -199,8 +202,8 @@ abstract class BaseStatisticTest {
* This method is used to cross-validate the statistic computation against the reference
* {@code double} implementation.
*
- * <p>The default implementation return {@link #getTolerance()}.
- *
+ * <p>The default implementation uses {@link #getTolerance()}.
+ *
* <p>Note: Computation using {@code double} values may not be as accurate as integer
* specialisations. This tolerance can be set appropriately to detect errors, for example
* using a relative tolerance of 1e-12.
@@ -307,7 +310,7 @@ abstract class BaseStatisticTest {
*
* @param s Statistic.
* @return the native result
- * @see #getNativeResult(IntStatistic)
+ * @see #getResultType()
*/
private Object getNativeResult(StatisticResult s) {
try {
@@ -328,4 +331,17 @@ abstract class BaseStatisticTest {
}
throw new IllegalStateException("Unrecognised result type: " + getResultType());
}
+
+ /**
+ * Re-throw the error wrapped in an AssertionError with a message that appends the seed
+ * and repeat for the random order test.
+ *
+ * @param e Error.
+ * @param seed Seed.
+ * @param repeat Repeat of the total random permutations.
+ */
+ static void rethrowWithSeedAndRepeat(AssertionError e, long[] seed, int repeat) {
+ throw new AssertionError(String.format("%s; Seed=%s; Repeat=%d/%d",
+ e.getMessage(), Arrays.toString(seed), repeat, RANDOM_PERMUTATIONS), e);
+ }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
new file mode 100644
index 0000000..042ef10
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/Int128Test.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link Int128}.
+ */
+class Int128Test {
+ private static final BigInteger TWO_POW_128 = BigInteger.ONE.shiftLeft(128);
+ private static final BigInteger TWO_POW_127 = BigInteger.ONE.shiftLeft(127);
+ private static final BigInteger MINUS_TWO_POW_127 = BigInteger.ONE.shiftLeft(127).negate();
+
+ @Test
+ void testCreate() {
+ final Int128 v = Int128.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testAddLong"})
+ void testToBigInteger(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).shiftLeft(64).add(BigInteger.valueOf(b));
+ final Int128 v = new Int128(a, b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final Int128 v = Int128.of(a);
+ v.add(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, 2, Long.MIN_VALUE, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ builder.accept(Arguments.of(i, -j));
+ builder.accept(Arguments.of(-i, j));
+ builder.accept(Arguments.of(-i, -j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final Int128 v = Int128.create();
+ for (final long x : a) {
+ v.add(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ TestHelper.assertEquals(new BigDecimal(expected), v.toDD(), 0x1.0p-106, "DD");
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> -(x >>> 2)).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, long b, long c, long d) {
+ final Int128 x = new Int128(a, b);
+ final Int128 y = new Int128(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo64());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The Int128 result is a signed 128-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [2^-127, 2^127).
+ // Since the overflow will be at most 1-bit we can wrap the value
+ // using +/- 2^128.
+ if (expected.compareTo(TWO_POW_127) >= 0) {
+ // too high
+ expected = expected.subtract(TWO_POW_128);
+ } else if (expected.compareTo(MINUS_TWO_POW_127) < 0) {
+ // too low
+ expected = expected.add(TWO_POW_128);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check floating-point representation
+ TestHelper.assertEquals(new BigDecimal(expected), x.toDD(), 0x1.0p-106, "DD");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.compareTo(TWO_POW_127) >= 0) {
+ // too high
+ expected = expected.subtract(TWO_POW_128);
+ } else if (expected.compareTo(MINUS_TWO_POW_127) < 0) {
+ // too low
+ expected = expected.add(TWO_POW_128);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 1, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSquareLow(long a) {
+ final BigInteger expected = BigInteger.valueOf(a).pow(2);
+ final UInt128 v = Int128.of(a).squareLow();
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static LongStream testSquareLow() {
+ final LongStream.Builder builder = LongStream.builder();
+ final long[] x = {0, 1, Long.MIN_VALUE, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ builder.accept(i);
+ builder.accept(-i);
+ }
+ RandomSource.XO_RO_SHI_RO_128_PP.create().longs(20).forEach(builder);
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
new file mode 100644
index 0000000..c390041
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMathTest.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.util.function.Supplier;
+import java.util.stream.DoubleStream;
+import java.util.stream.LongStream;
+import java.util.stream.LongStream.Builder;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Test for {@link IntMath}.
+ */
+class IntMathTest {
+ /** 2^63. */
+ private static final BigInteger TWO_POW_63 = BigInteger.ONE.shiftLeft(63);
+
+ @ParameterizedTest
+ @MethodSource
+ void testSquareHigh(long a) {
+ final long actual = IntMath.squareHigh(a);
+ final long expected = BigInteger.valueOf(a).pow(2).shiftRight(64).longValue();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ static LongStream testSquareHigh() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Builder builder = LongStream.builder();
+ builder.accept(0);
+ builder.accept(Long.MAX_VALUE);
+ builder.accept(Long.MIN_VALUE);
+ rng.ints(5).forEach(builder::accept);
+ rng.longs(50).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 1).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 2).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 5).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 13).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 35).forEach(builder::accept);
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testUnsignedMultiplyHigh(long a, long b) {
+ final long actual = IntMath.unsignedMultiplyHigh(a, b);
+ final BigInteger bi1 = toUnsignedBigInteger(a);
+ final BigInteger bi2 = toUnsignedBigInteger(b);
+ final BigInteger expected = bi1.multiply(bi2);
+ Assertions.assertEquals(expected.shiftRight(Long.SIZE).longValue(), actual,
+ () -> String.format("%s * %s", bi1, bi2));
+ final double x = expected.doubleValue();
+ Assertions.assertEquals(x, IntMath.unsignedMultiplyToDouble(a, b),
+ () -> String.format("double2 %s * %s", bi1, bi2));
+ }
+
+ static Stream<Arguments> testUnsignedMultiplyHigh() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] values = {
+ -1, 0, 1, Long.MAX_VALUE, Long.MIN_VALUE,
+ 0xffL, 0xff00L, 0xff0000L, 0xff000000L,
+ 0xff00000000L, 0xff0000000000L, 0xff000000000000L, 0xff000000000000L,
+ 0xffffL, 0xffff0000L, 0xffff00000000L, 0xffff000000000000L,
+ 0xffffffffL, 0xffffffff00000000L
+ };
+ for (final long v1 : values) {
+ for (final long v2 : values) {
+ builder.accept(Arguments.of(v1, v2));
+ builder.accept(Arguments.of(v1 >>> 15, v2 >>> 18));
+ }
+ }
+ for (int i = 0; i < 200; i++) {
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Create a big integer treating the value as unsigned.
+ *
+ * @param v Value
+ * @return the big integer
+ */
+ private static BigInteger toUnsignedBigInteger(long v) {
+ return v < 0 ?
+ TWO_POW_63.add(BigInteger.valueOf(v & Long.MAX_VALUE)) :
+ BigInteger.valueOf(v);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testUin128ToDouble(long a, long b) {
+ final BigInteger bi1 = toUnsignedBigInteger(a).shiftLeft(Long.SIZE);
+ final BigInteger bi2 = toUnsignedBigInteger(b);
+ final double x = bi1.add(bi2).doubleValue();
+ Assertions.assertEquals(x, IntMath.uin128ToDouble(a, b),
+ () -> String.format("%s + %s", a, b));
+ }
+
+ static Stream<Arguments> testUin128ToDouble() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ for (int i = 0; i < 100; i++) {
+ long a = rng.nextLong();
+ long b = rng.nextLong();
+ builder.accept(Arguments.of(a, b));
+ builder.accept(Arguments.of(0, b));
+ // Edge cases where trailing bits are required for rounding.
+ // Create a 55-bit number. Ensure the highest bit is set.
+ a = (a << 9) | Long.MIN_VALUE;
+ // Shift right and carry bits down.
+ int shift = rng.nextInt(1, 64);
+ long c = a >>> shift;
+ long d = a << -shift;
+ // Check
+ Assertions.assertEquals(Long.bitCount(a), Long.bitCount(c) + Long.bitCount(d));
+ builder.accept(Arguments.of(c, d));
+ // Add a trailing bit that may change rounding
+ builder.accept(Arguments.of(c, d | 1));
+ // Repeat for special case of a 64-bit unsigned integer
+ builder.accept(Arguments.of(0, a | 1));
+ }
+ // At least one case where the trailing bit does effect rounding
+ // 54-bits all set is an odd number + 0.5
+ builder.accept(Arguments.of(1, (1L << 11)));
+ builder.accept(Arguments.of(1, (1L << 11) | 1));
+ // Unset the second to last bit and repeat above is an even number + 0.5
+ builder.accept(Arguments.of(1, ((1L & ~0x2) << 11)));
+ builder.accept(Arguments.of(1, ((1L & ~0x2) << 11) | 1));
+ return builder.build();
+ }
+
+ /**
+ * Test round-to-int exact matches the result generated using JDK Math functions.
+ * The only exception is NaN which will round to zero (see {@link #testToIntExactNaN()}.
+ *
+ * <p>Note: Rounding for all types is tested in StatisticResultTest.
+ */
+ @ParameterizedTest
+ @MethodSource
+ @ValueSource(doubles = {-1.265384, 67.346578, 72893.5, -42.678,
+ Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY})
+ void testToIntExact(double x) {
+ for (final int sign : new int[] {-1, 1}) {
+ final double y = x * sign;
+ final Supplier<String> intMsg = () -> String.valueOf(y);
+ Integer i = null;
+ try {
+ i = Math.toIntExact(Math.round(y));
+ } catch (Throwable t) {
+ Assertions.assertThrowsExactly(t.getClass(), () -> IntMath.toIntExact(y), intMsg);
+ }
+ if (i != null) {
+ Assertions.assertEquals(i.intValue(), IntMath.toIntExact(y), intMsg);
+ }
+ }
+ }
+
+ static DoubleStream testToIntExact() {
+ DoubleStream.Builder builder = DoubleStream.builder();
+ for (double x = 0; x < 3.5; x += 0.25) {
+ builder.accept(x);
+ }
+ for (double x = -1.5; x <= 1.5; x += 0.25) {
+ builder.accept(x + Integer.MAX_VALUE);
+ builder.accept(x + Integer.MIN_VALUE);
+ }
+ return builder.build();
+ }
+
+ /**
+ * Test round-to-int exact does not match the result generated using JDK Math functions
+ * for NaN.
+ *
+ * <p>Note: Rounding for all types is tested in StatisticResultTest.
+ */
+ @Test
+ void testToIntExactNaN() {
+ final double x = Double.NaN;
+ Assertions.assertEquals(0, Math.toIntExact(Math.round(x)));
+ Assertions.assertThrowsExactly(ArithmeticException.class, () -> IntMath.toIntExact(x));
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.java
new file mode 100644
index 0000000..60d5449
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntMeanTest.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.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerance;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/**
+ * Test for {@link IntMean}.
+ */
+final class IntMeanTest extends BaseIntStatisticTest<IntMean> {
+
+ @Override
+ protected IntMean create() {
+ return IntMean.create();
+ }
+
+ @Override
+ protected IntMean create(int... values) {
+ return IntMean.of(values);
+ }
+
+ @Override
+ protected DoubleStatistic createAsDoubleStatistic(int... values) {
+ return Mean.of(Arrays.stream(values).asDoubleStream().toArray());
+ }
+
+ @Override
+ protected DoubleTolerance getToleranceAsDouble() {
+ // Large shifts in the rolling mean are not computed very accurately
+ return DoubleTolerances.relative(5e-8);
+ }
+
+ @Override
+ protected StatisticResult getEmptyValue() {
+ return createStatisticResult(Double.NaN);
+ }
+
+ @Override
+ protected StatisticResult getExpectedValue(int[] values) {
+ // Use the JDK as a reference implementation
+ final double x = Arrays.stream(values).average().orElse(Double.NaN);
+ return createStatisticResult(x);
+ }
+
+ @Override
+ protected DoubleTolerance getTolerance() {
+ return DoubleTolerances.equals();
+ }
+
+ @Override
+ protected Stream<StatisticTestData> streamTestData() {
+ final Stream.Builder<StatisticTestData> builder = Stream.builder();
+ builder.accept(addCase(Integer.MAX_VALUE - 1, Integer.MAX_VALUE));
+ builder.accept(addCase(Integer.MIN_VALUE + 1, Integer.MIN_VALUE));
+ final int[] a = new int[2 * 512 * 512];
+ Arrays.fill(a, 0, a.length / 2, 10);
+ Arrays.fill(a, a.length / 2, a.length, 1);
+ builder.accept(addReference(5.5, a));
+
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Python Numpy v1.25.1: numpy.mean
+ builder.accept(addReference(2.5, tol, 1, 2, 3, 4));
+ builder.accept(addReference(12.0, tol, 5, 9, 13, 14, 10, 12, 11, 15, 19));
+ // R v4.3.1: mean(x)
+ builder.accept(addReference(5.5, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+ builder.accept(addReference(8.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50));
+ return builder.build();
+ }
+
+ /**
+ * Test a large integer sums that overflow a {@code long}.
+ * Overflow is created by repeat addition.
+ *
+ * <p>Note: Currently no check is made for overflow in the
+ * count of observations. If this overflows then the statistic
+ * will be incorrect so the test is limited to {@code n < 2^63}.
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "-1628367811, -516725738, 60",
+ "627834682, 456456670, 61",
+ "2147483647, 2147483646, 61",
+ "-2147483648, -2147483647, 61",
+ })
+ void testLongOverflow(int x, int y, int exp) {
+ final IntMean s = IntMean.of(x, y);
+ final double mean = ((long) x + y) * 0.5;
+ for (int i = 0; i < exp; i++) {
+ // Assumes the sum as a long will overflow
+ s.combine(s);
+ Assertions.assertEquals(mean, s.getAsDouble());
+ }
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntVarianceTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntVarianceTest.java
new file mode 100644
index 0000000..5eea9e8
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/IntVarianceTest.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerance;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
+import org.apache.commons.statistics.distribution.TestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link IntVariance}.
+ */
+final class IntVarianceTest extends BaseIntStatisticTest<IntVariance> {
+
+ @Override
+ protected IntVariance create() {
+ return IntVariance.create();
+ }
+
+ @Override
+ protected IntVariance create(int... values) {
+ return IntVariance.of(values);
+ }
+
+ @Override
+ protected DoubleStatistic createAsDoubleStatistic(int... values) {
+ return Variance.of(Arrays.stream(values).asDoubleStream().toArray());
+ }
+
+ @Override
+ protected DoubleTolerance getToleranceAsDouble() {
+ return DoubleTolerances.ulps(20);
+ }
+
+ @Override
+ protected StatisticResult getEmptyValue() {
+ return createStatisticResult(Double.NaN);
+ }
+
+ @Override
+ protected StatisticResult getExpectedValue(int[] values) {
+ if (values.length == 1) {
+ return createStatisticResult(0.0);
+ }
+ final long s = Arrays.stream(values).asLongStream().sum();
+ final BigInteger ss = Arrays.stream(values)
+ .mapToObj(i -> BigInteger.valueOf((long) i * i))
+ .reduce(BigInteger.ZERO, BigInteger::add);
+ final MathContext mc = MathContext.DECIMAL128;
+ final int n = values.length;
+ // var = (n * sum(x^2) - sum(x)^2) / (n * (n-1))
+ // Exact numerator
+ final BigInteger num = ss.multiply(BigInteger.valueOf(n)).subtract(
+ BigInteger.valueOf(s).pow(2));
+ // Exact divide
+ final double x = new BigDecimal(num)
+ .divide(BigDecimal.valueOf(n * (n - 1L)), mc)
+ .doubleValue();
+ return createStatisticResult(x);
+ }
+
+ @Override
+ protected DoubleTolerance getTolerance() {
+ return DoubleTolerances.equals();
+ }
+
+ @Override
+ protected Stream<StatisticTestData> streamTestData() {
+ final Stream.Builder<StatisticTestData> builder = Stream.builder();
+ builder.accept(addCase(Integer.MAX_VALUE - 1, Integer.MAX_VALUE));
+ builder.accept(addCase(Integer.MIN_VALUE + 1, Integer.MIN_VALUE));
+
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Python Numpy v1.25.1: numpy.var(x, ddof=1)
+ builder.accept(addReference(1.6666666666666667, tol, 1, 2, 3, 4));
+ builder.accept(addReference(7.454545454545454, tol,
+ 14, 8, 11, 10, 7, 9, 10, 11, 10, 15, 5, 10));
+ // R v4.3.1: var(x)
+ builder.accept(addReference(9.166666666666666, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+ builder.accept(addReference(178.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testBiased(int[] values, double biased, double unbiased, DoubleTolerance tol) {
+ final IntVariance stat = IntVariance.of(values);
+ // Default is unbiased
+ final double actualUnbiased = stat.getAsDouble();
+ TestUtils.assertEquals(unbiased, actualUnbiased, tol, () -> "Unbiased: " + format(values));
+ Assertions.assertSame(stat, stat.setBiased(true));
+ final double acutalBiased = stat.getAsDouble();
+ TestUtils.assertEquals(biased, acutalBiased, tol, () -> "Biased: " + format(values));
+ // The mutable state can be switched back and forth
+ Assertions.assertSame(stat, stat.setBiased(false));
+ Assertions.assertEquals(actualUnbiased, stat.getAsDouble(), () -> "Unbiased: " + format(values));
+ Assertions.assertSame(stat, stat.setBiased(true));
+ Assertions.assertEquals(acutalBiased, stat.getAsDouble(), () -> "Biased: " + format(values));
+ }
+
+ static Stream<Arguments> testBiased() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Note: Biased variance is ((10-5.5)**2 + (1-5.5)**2)/2 = 20.25
+ // Scale by (2 * 512 * 512) / (2 * 512 * 512 - 1)
+ // The variance is invariant to shift
+ final int shift = 253674678;
+ final int[] a = new int[2 * 512 * 512];
+ Arrays.fill(a, 0, a.length / 2, 10 + shift);
+ Arrays.fill(a, a.length / 2, a.length, 1 + shift);
+ builder.accept(Arguments.of(a, 20.25, 20.250038623883484, tol));
+
+ // Python Numpy v1.25.1: numpy.var(x, ddof=0/1)
+ // Note: Numpy allows other degrees of freedom adjustment than 0 or 1.
+ builder.accept(Arguments.of(new int[] {1, 2, 3}, 0.6666666666666666, 1, tol));
+ builder.accept(Arguments.of(new int[] {1, 2}, 0.25, 0.5, tol));
+ // Matlab R2023s: var(x, 1/0)
+ // Matlab only allows turning the biased option on (1) or off (0).
+ // Note: Numpy will return NaN for ddof=1 when the array length is 1 (since 0 / 0 = NaN).
+ // This implementation matches the behaviour of Matlab which returns zero.
+ builder.accept(Arguments.of(new int[] {1}, 0, 0, tol));
+ builder.accept(Arguments.of(new int[] {1, 2, 4, 8}, 7.1875, 9.583333333333334, tol));
+ return builder.build();
+ }
+
+ /**
+ * Test a large integer sums that overflow a {@code long}.
+ * Overflow is created by repeat addition.
+ *
+ * <p>Note: Currently no check is made for overflow in the
+ * count of observations. If this overflows then the statistic
+ * will be incorrect so the test is limited to {@code n < 2^63}.
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "-1628367811, -516725738, 60",
+ "627834682, 456456670, 61",
+ "2147483647, 2147483646, 61",
+ "-2147483648, -2147483647, 61",
+ })
+ void testLongOverflow(int x, int y, int exp) {
+ final IntVariance s = IntVariance.of(x, y);
+ // var = sum((x - mean)^2) / (n-1)
+ // = (n * sum(x^2) - sum(x)^2) / (n * (n-1))
+ long n = 2;
+ BigInteger term1 = BigInteger.valueOf((long) x * x).add(BigInteger.valueOf((long) y * y));
+ BigInteger term2 = BigInteger.valueOf((long) x + y);
+ final DoubleTolerance tol = DoubleTolerances.ulps(2);
+ for (int i = 0; i < exp; i++) {
+ // Assumes the sum as a long will overflow
+ s.combine(s);
+ n <<= 1;
+ term1 = term1.add(term1);
+ term2 = term2.add(term2);
+ final double expected = new BigDecimal(
+ term1.multiply(BigInteger.valueOf(n)).subtract(term2.pow(2)))
+ .divide(
+ new BigDecimal(BigInteger.valueOf(n).multiply(BigInteger.valueOf(n - 1))),
+ MathContext.DECIMAL128)
+ .doubleValue();
+ TestUtils.assertEquals(expected, s.getAsDouble(), tol);
+ }
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
new file mode 100644
index 0000000..0d0f86b
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongMeanTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerance;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+/**
+ * Test for {@link LongMean}.
+ */
+final class LongMeanTest extends BaseLongStatisticTest<LongMean> {
+
+ @Override
+ protected LongMean create() {
+ return LongMean.create();
+ }
+
+ @Override
+ protected LongMean create(long... values) {
+ return LongMean.of(values);
+ }
+
+ @Override
+ protected StatisticResult getEmptyValue() {
+ return createStatisticResult(Double.NaN);
+ }
+
+ @Override
+ protected DoubleStatistic createAsDoubleStatistic(long... values) {
+ return Mean.of(Arrays.stream(values).asDoubleStream().toArray());
+ }
+
+ @Override
+ protected DoubleTolerance getToleranceAsDouble() {
+ // Data with large shifts in the rolling mean is not computed very accurately
+ return DoubleTolerances.relative(5e-8);
+ }
+
+ @Override
+ protected StatisticResult getExpectedValue(long[] values) {
+ final BigInteger sum = Arrays.stream(values)
+ .mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger.ZERO, BigInteger::add);
+ final double x = new BigDecimal(sum)
+ .divide(BigDecimal.valueOf(values.length), MathContext.DECIMAL128)
+ .doubleValue();
+ return createStatisticResult(x);
+ }
+
+ @Override
+ protected DoubleTolerance getTolerance() {
+ return DoubleTolerances.equals();
+ }
+
+ @Override
+ protected Stream<StatisticTestData> streamTestData() {
+ final Stream.Builder<StatisticTestData> builder = Stream.builder();
+ builder.accept(addCase(Long.MAX_VALUE - 1, Long.MAX_VALUE));
+ builder.accept(addCase(Long.MIN_VALUE + 1, Long.MIN_VALUE));
+ final long[] a = new long[2 * 512 * 512];
+ Arrays.fill(a, 0, a.length / 2, 10);
+ Arrays.fill(a, a.length / 2, a.length, 1);
+ builder.accept(addReference(5.5, a));
+
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Python Numpy v1.25.1: numpy.mean
+ builder.accept(addReference(2.5, tol, 1, 2, 3, 4));
+ builder.accept(addReference(12.0, tol, 5, 9, 13, 14, 10, 12, 11, 15, 19));
+ // R v4.3.1: mean(x)
+ builder.accept(addReference(5.5, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+ builder.accept(addReference(8.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50));
+ return builder.build();
+ }
+
+ /**
+ * Test a large integer sums that overflow a {@code long}.
+ * Overflow is created by repeat addition.
+ *
+ * <p>Note: Currently no check is made for overflow in the
+ * count of observations. If this overflows then the statistic
+ * will be incorrect so the test is limited to {@code n < 2^63}.
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "-1628367672438123811, -97927322516725738, 60",
+ "3279208082627834682, 4234564566706285432, 61",
+ "9223372036854775807, 9223372036854775806, 61",
+ "-9223372036854775808, -9223372036854775807, 61",
+ })
+ void testLongOverflow(long x, long y, int exp) {
+ final LongMean s = LongMean.of(x, y);
+ final double mean = BigInteger.valueOf(x)
+ .add(BigInteger.valueOf(y)).doubleValue() * 0.5;
+ for (int i = 0; i < exp; i++) {
+ // Assumes the sum as a long will overflow
+ s.combine(s);
+ Assertions.assertEquals(mean, s.getAsDouble());
+ }
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongVarianceTest.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongVarianceTest.java
new file mode 100644
index 0000000..b17c2f4
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/LongVarianceTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.statistics.distribution.DoubleTolerance;
+import org.apache.commons.statistics.distribution.DoubleTolerances;
+import org.apache.commons.statistics.distribution.TestUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link LongVariance}.
+ */
+final class LongVarianceTest extends BaseLongStatisticTest<LongVariance> {
+
+ @Override
+ protected LongVariance create() {
+ return LongVariance.create();
+ }
+
+ @Override
+ protected LongVariance create(long... values) {
+ return LongVariance.of(values);
+ }
+
+ @Override
+ protected DoubleStatistic createAsDoubleStatistic(long... values) {
+ if (values.length == 0) {
+ return Variance.create();
+ }
+ // Detect cases where all the values are the same double (no variance)
+ // and map values with a shift.
+ if (values.length > 1) {
+ final double first = values[0];
+ if (!Arrays.stream(values).asDoubleStream().filter(x -> x != first).findAny().isPresent()) {
+ final long shift = Arrays.stream(values).min().orElse(0);
+ values = Arrays.stream(values).map(x -> x - shift).toArray();
+ }
+ }
+ return Variance.of(Arrays.stream(values).asDoubleStream().toArray());
+ }
+
+ @Override
+ protected DoubleTolerance getToleranceAsDouble() {
+ return DoubleTolerances.ulps(20);
+ }
+
+ @Override
+ protected StatisticResult getEmptyValue() {
+ return createStatisticResult(Double.NaN);
+ }
+
+ @Override
+ protected StatisticResult getExpectedValue(long[] values) {
+ if (values.length == 1) {
+ return createStatisticResult(0.0);
+ }
+ final BigInteger s = Arrays.stream(values).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger.ZERO, BigInteger::add);
+ final BigInteger ss = Arrays.stream(values)
+ .mapToObj(i -> BigInteger.valueOf(i).pow(2))
+ .reduce(BigInteger.ZERO, BigInteger::add);
+ final MathContext mc = MathContext.DECIMAL128;
+ final int n = values.length;
+ // var = (n * sum(x^2) - sum(x)^2) / (n * (n-1))
+ // Exact numerator
+ final BigInteger num = ss.multiply(BigInteger.valueOf(n)).subtract(s.pow(2));
+ // Exact divide
+ final double x = new BigDecimal(num)
+ .divide(BigDecimal.valueOf(n * (n - 1L)), mc)
+ .doubleValue();
+ return createStatisticResult(x);
+ }
+
+ @Override
+ protected DoubleTolerance getTolerance() {
+ return DoubleTolerances.equals();
+ }
+
+ @Override
+ protected Stream<StatisticTestData> streamTestData() {
+ final Stream.Builder<StatisticTestData> builder = Stream.builder();
+ builder.accept(addCase(Long.MAX_VALUE - 1, Long.MAX_VALUE));
+ builder.accept(addCase(Long.MIN_VALUE + 1, Long.MIN_VALUE));
+
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Python Numpy v1.25.1: numpy.var(x, ddof=1)
+ builder.accept(addReference(1.6666666666666667, tol, 1, 2, 3, 4));
+ builder.accept(addReference(7.454545454545454, tol,
+ 14, 8, 11, 10, 7, 9, 10, 11, 10, 15, 5, 10));
+ // R v4.3.1: var(x)
+ builder.accept(addReference(9.166666666666666, tol, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
+ builder.accept(addReference(178.75, tol, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testBiased(long[] values, double biased, double unbiased, DoubleTolerance tol) {
+ final LongVariance stat = LongVariance.of(values);
+ // Default is unbiased
+ final double actualUnbiased = stat.getAsDouble();
+ TestUtils.assertEquals(unbiased, actualUnbiased, tol, () -> "Unbiased: " + format(values));
+ Assertions.assertSame(stat, stat.setBiased(true));
+ final double acutalBiased = stat.getAsDouble();
+ TestUtils.assertEquals(biased, acutalBiased, tol, () -> "Biased: " + format(values));
+ // The mutable state can be switched back and forth
+ Assertions.assertSame(stat, stat.setBiased(false));
+ Assertions.assertEquals(actualUnbiased, stat.getAsDouble(), () -> "Unbiased: " + format(values));
+ Assertions.assertSame(stat, stat.setBiased(true));
+ Assertions.assertEquals(acutalBiased, stat.getAsDouble(), () -> "Biased: " + format(values));
+ }
+
+ static Stream<Arguments> testBiased() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ // Same cases as for the DoubleStatistic Variance but the tolerance is exact
+ final DoubleTolerance tol = DoubleTolerances.equals();
+
+ // Note: Biased variance is ((10-5.5)**2 + (1-5.5)**2)/2 = 20.25
+ // Scale by (2 * 512 * 512) / (2 * 512 * 512 - 1)
+ // The variance is invariant to shift
+ final long shift = -1379182644762676428L;
+ final long[] a = new long[2 * 512 * 512];
+ Arrays.fill(a, 0, a.length / 2, 10 + shift);
+ Arrays.fill(a, a.length / 2, a.length, 1 + shift);
+ builder.accept(Arguments.of(a, 20.25, 20.250038623883484, tol));
+
+ // Python Numpy v1.25.1: numpy.var(x, ddof=0/1)
+ // Note: Numpy allows other degrees of freedom adjustment than 0 or 1.
+ builder.accept(Arguments.of(new long[] {1, 2, 3}, 0.6666666666666666, 1, tol));
+ builder.accept(Arguments.of(new long[] {1, 2}, 0.25, 0.5, tol));
+ // Matlab R2023s: var(x, 1/0)
+ // Matlab only allows turning the biased option on (1) or off (0).
+ // Note: Numpy will return NaN for ddof=1 when the array length is 1 (since 0 / 0 = NaN).
+ // This implementation matches the behaviour of Matlab which returns zero.
+ builder.accept(Arguments.of(new long[] {1}, 0, 0, tol));
+ builder.accept(Arguments.of(new long[] {1, 2, 4, 8}, 7.1875, 9.583333333333334, tol));
+ return builder.build();
+ }
+
+ /**
+ * Test a large integer sums that overflow a {@code long}.
+ * Overflow is created by repeat addition.
+ *
+ * <p>Note: Currently no check is made for overflow in the
+ * count of observations. If this overflows then the statistic
+ * will be incorrect so the test is limited to {@code n < 2^63}.
+ */
+ @ParameterizedTest
+ @CsvSource({
+ "-1628367672438123811, -97927322516725738, 60",
+ "3279208082627834682, 4234564566706285432, 61",
+ "9223372036854775807, 9223372036854775806, 61",
+ "-9223372036854775808, -9223372036854775807, 61",
+ })
+ void testLongOverflow(long x, long y, int exp) {
+ final LongVariance s = LongVariance.of(x, y);
+ // var = sum((x - mean)^2) / (n-1)
+ // = (n * sum(x^2) - sum(x)^2) / (n * (n-1))
+ long n = 2;
+ BigInteger term1 = BigInteger.valueOf(x).pow(2).add(BigInteger.valueOf(y).pow(2));
+ BigInteger term2 = BigInteger.valueOf(x).add(BigInteger.valueOf(y));
+ final DoubleTolerance tol = DoubleTolerances.ulps(2);
+ for (int i = 0; i < exp; i++) {
+ // Assumes the sum as a long will overflow
+ s.combine(s);
+ n <<= 1;
+ term1 = term1.add(term1);
+ term2 = term2.add(term2);
+ final double expected = new BigDecimal(
+ term1.multiply(BigInteger.valueOf(n)).subtract(term2.pow(2)))
+ .divide(
+ new BigDecimal(BigInteger.valueOf(n).multiply(BigInteger.valueOf(n - 1))),
+ MathContext.DECIMAL128)
+ .doubleValue();
+ TestUtils.assertEquals(expected, s.getAsDouble(), tol);
+ }
+ }
+}
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 2eb35e9..4df0044 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
@@ -21,6 +21,7 @@ import java.math.BigInteger;
import java.math.MathContext;
import java.util.Arrays;
import java.util.function.Supplier;
+import org.apache.commons.numbers.core.DD;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.statistics.distribution.DoubleTolerance;
@@ -447,4 +448,131 @@ final class TestHelper {
}
}
}
+
+ // DD equality checks adapted from o.a.c.numbers.core.TestUtils
+
+ /**
+ * Assert the two numbers are equal within the provided relative error.
+ *
+ * <p>The provided error is relative to the exact result in expected: (e - a) / e.
+ * If expected is zero this division is undefined. In this case the actual must be zero
+ * (no absolute tolerance is supported). The reporting of the error uses the absolute
+ * error and the return value of the relative error is 0. Cases of complete cancellation
+ * should be avoided for benchmarking relative accuracy.
+ *
+ * <p>Note that the actual double-double result is not validated using the high and low
+ * parts individually. These are summed and compared to the expected.
+ *
+ * <p>Set {@code eps} to negative to report the relative error to the stdout and
+ * ignore failures.
+ *
+ * <p>The relative error is signed. The sign of the error
+ * is the same as that returned from Double.compare(actual, expected); it is
+ * computed using {@code actual - expected}.
+ *
+ * @param expected expected value
+ * @param actual actual value
+ * @param eps maximum relative error between the two values
+ * @param msg failure message
+ * @return relative error difference between the values (signed)
+ * @throws NumberFormatException if {@code actual} contains non-finite values
+ */
+ static double assertEquals(BigDecimal expected, DD actual, double eps, String msg) {
+ return assertEquals(expected, actual, eps, () -> msg);
+ }
+
+ /**
+ * Assert the two numbers are equal within the provided relative error.
+ *
+ * <p>The provided error is relative to the exact result in expected: (e - a) / e.
+ * If expected is zero this division is undefined. In this case the actual must be zero
+ * (no absolute tolerance is supported). The reporting of the error uses the absolute
+ * error and the return value of the relative error is 0. Cases of complete cancellation
+ * should be avoided for benchmarking relative accuracy.
+ *
+ * <p>Note that the actual double-double result is not validated using the high and low
+ * parts individually. These are summed and compared to the expected.
+ *
+ * <p>Set {@code eps} to negative to report the relative error to the stdout and
+ * ignore failures.
+ *
+ * <p>The relative error is signed. The sign of the error
+ * is the same as that returned from Double.compare(actual, expected); it is
+ * computed using {@code actual - expected}.
+ *
+ * @param expected expected value
+ * @param actual actual value
+ * @param eps maximum relative error between the two values
+ * @param msg failure message
+ * @return relative error difference between the values (signed)
+ * @throws NumberFormatException if {@code actual} contains non-finite values
+ */
+ static double assertEquals(BigDecimal expected, DD actual, double eps, Supplier<String> msg) {
+ // actual - expected
+ final BigDecimal delta = new BigDecimal(actual.hi())
+ .add(new BigDecimal(actual.lo()))
+ .subtract(expected);
+ boolean equal;
+ if (expected.compareTo(BigDecimal.ZERO) == 0) {
+ // Edge case. Currently an absolute tolerance is not supported as summation
+ // to zero cases generated in testing all pass.
+ equal = actual.doubleValue() == 0;
+
+ // DEBUG:
+ if (eps < 0) {
+ if (!equal) {
+ printf("%sexpected 0 != actual <%s + %s> (abs.error=%s)%n",
+ prefix(msg), actual.hi(), actual.lo(), delta.doubleValue());
+ }
+ } else if (!equal) {
+ Assertions.fail(String.format("%sexpected 0 != actual <%s + %s> (abs.error=%s)",
+ prefix(msg), actual.hi(), actual.lo(), delta.doubleValue()));
+ }
+
+ return 0;
+ }
+
+ final double rel = delta.divide(expected, MathContext.DECIMAL128).doubleValue();
+ // Allow input of a negative maximum ULPs
+ equal = Math.abs(rel) <= Math.abs(eps);
+
+ // DEBUG:
+ if (eps < 0) {
+ if (!equal) {
+ printf("%sexpected <%s> != actual <%s + %s> (rel.error=%s (%.3f x tol))%n",
+ prefix(msg), expected.round(MathContext.DECIMAL128), actual.hi(), actual.lo(),
+ rel, Math.abs(rel) / eps);
+ }
+ } else if (!equal) {
+ Assertions.fail(String.format("%sexpected <%s> != actual <%s + %s> (rel.error=%s (%.3f x tol))",
+ prefix(msg), expected.round(MathContext.DECIMAL128), actual.hi(), actual.lo(),
+ rel, Math.abs(rel) / eps));
+ }
+
+ return rel;
+ }
+
+ /**
+ * Print a formatted message to stdout.
+ * Provides a single point to disable checkstyle warnings on print statements and
+ * enable/disable all print debugging.
+ *
+ * @param format Format string.
+ * @param args Arguments.
+ */
+ static void printf(String format, Object... args) {
+ // CHECKSTYLE: stop regex
+ System.out.printf(format, args);
+ // CHECKSTYLE: resume regex
+ }
+
+ /**
+ * Get the prefix for the message.
+ *
+ * @param msg Message supplier
+ * @return the prefix
+ */
+ static String prefix(Supplier<String> msg) {
+ return msg == null ? "" : msg.get() + ": ";
+ }
}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt128Test.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt128Test.java
new file mode 100644
index 0000000..22f3d5f
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt128Test.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt128}.
+ */
+class UInt128Test {
+ private static final BigInteger TWO_POW_128 = BigInteger.ONE.shiftLeft(128);
+
+ @Test
+ void testCreate() {
+ final UInt128 v = UInt128.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @Test
+ void testAddLongMinValue() {
+ final UInt128 v = new UInt128(0, 1268361283468345237L);
+ final BigInteger x = BigInteger.ONE.shiftLeft(63);
+ BigInteger expected = v.toBigInteger();
+ for (int i = 1; i <= 5; i++) {
+ // Accepts a negative value without exception. This is
+ // computed correctly if the current low 32 bits
+ // added to the argument do not overflow. This is always
+ // true for min value as all lower 32-bits are zero.
+ v.addPositive(Long.MIN_VALUE);
+ expected = expected.add(x);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final UInt128 v = new UInt128(0, a);
+ v.addPositive(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt128 v = UInt128.create();
+ for (final long x : a) {
+ Assertions.assertFalse(x < 0, "Value must be positive");
+ v.addPositive(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ Assertions.assertEquals(expected.doubleValue(), v.toDouble(), "double");
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, long b, long c, long d) {
+ final UInt128 x = new UInt128(a, b);
+ final UInt128 y = new UInt128(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo64());
+ Assertions.assertEquals((int) (b >>> Integer.SIZE), x.mid32());
+ Assertions.assertEquals((int) b, x.lo32());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 128-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^128).
+ if (expected.testBit(128)) {
+ expected = expected.flipBit(128);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check floating-point representation
+ Assertions.assertEquals(expected.doubleValue(), x.toDouble(), "double");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(128)) {
+ expected = expected.flipBit(128);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 1, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testOfInt96(long a, int b) {
+ final UInt96 x = new UInt96(a, b);
+ final UInt128 y = UInt128.of(x);
+ Assertions.assertEquals(x.toBigInteger(), y.toBigInteger());
+ }
+
+ static Stream<Arguments> testOfInt96() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final int b = rng.nextInt();
+ builder.accept(Arguments.of(a, b));
+ builder.accept(Arguments.of(0, b));
+ builder.accept(Arguments.of(a, 0));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testMultiplyInt(long a, long b, int n) {
+ assertMultiplyInt(a, b, n);
+ assertMultiplyInt(a >>> 32, b, n);
+ assertMultiplyInt(0, b, n);
+ }
+
+ private static void assertMultiplyInt(long a, long b, int n) {
+ final UInt128 v = new UInt128(a, b);
+ BigInteger expected = v.toBigInteger().multiply(BigInteger.valueOf(n & 0xffff_ffffL));
+ // Clip to 128-bits. Only required if the upper 32-bits are non-zero.
+ final int len = expected.bitLength();
+ if (len > 128 && (v.hi64() >>> Integer.SIZE) != 0) {
+ expected = expected.subtract(expected.shiftRight(128).shiftLeft(128));
+ }
+ Assertions.assertEquals(expected, v.unsignedMultiply(n).toBigInteger());
+ }
+
+ static Stream<Arguments> testMultiplyInt() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final int[] x = {0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ for (final int n : x) {
+ builder.accept(Arguments.of(a, b, n));
+ }
+ for (int j = 0; j < 5; j++) {
+ builder.accept(Arguments.of(a, b, rng.nextInt()));
+ }
+ }
+ builder.accept(Arguments.of(-1L >>> 32, -1L, -1));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSubtract(long a, long b, long c, long d) {
+ assertSubtract(a, b, c, d);
+ assertSubtract(c, d, a, b);
+ }
+
+ private static void assertSubtract(long a, long b, long c, long d) {
+ final UInt128 x = new UInt128(a, b);
+ final UInt128 y = new UInt128(c, d);
+ BigInteger expected = x.toBigInteger().subtract(y.toBigInteger());
+ if (expected.signum() < 0) {
+ expected = expected.add(TWO_POW_128);
+ }
+ Assertions.assertEquals(expected, x.subtract(y).toBigInteger());
+ }
+
+ static Stream<Arguments> testSubtract() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ final long d = rng.nextLong();
+ builder.accept(Arguments.of(a, b, c, d));
+ builder.accept(Arguments.of(0, 0, c, d));
+ builder.accept(Arguments.of(-1L, -1L, c, d));
+ }
+ builder.accept(Arguments.of(-1L, -1L, -1L, -1L));
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt192Test.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt192Test.java
new file mode 100644
index 0000000..8456855
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt192Test.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt192}.
+ */
+class UInt192Test {
+ private static final BigInteger TWO_POW_192 = BigInteger.ONE.shiftLeft(192);
+
+ @Test
+ void testCreate() {
+ final UInt192 v = UInt192.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddSquareLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).pow(2)
+ .add(BigInteger.valueOf(b).pow(2));
+ final UInt192 v = UInt192.create();
+ v.addSquare(a);
+ v.addSquare(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddSquareLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 61278342166787978L, 42, 8652939272947492397L};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddSquareLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .map(x -> x.pow(2))
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt192 v = UInt192.create();
+ for (final long x : a) {
+ v.addSquare(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ Assertions.assertEquals(expected.doubleValue(), v.toDouble(), "double");
+ }
+
+ static Stream<Arguments> testAddSquareLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt192(long a, long b, long c, long d, long e, long f) {
+ final UInt192 x = new UInt192(a, b, c);
+ final UInt192 y = new UInt192(d, e, f);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.mid64());
+ Assertions.assertEquals(c, x.lo64());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 192-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^192).
+ if (expected.testBit(192)) {
+ expected = expected.flipBit(192);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d, %d) + (%d, %d, %d)", a, b, c, d, e, f));
+ // Check floating-point representation
+ Assertions.assertEquals(expected.doubleValue(), x.toDouble(), "double");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(192)) {
+ expected = expected.flipBit(192);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d, %d) self-addition", d, e, f));
+ }
+
+ static Stream<Arguments> testAddInt192() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(),
+ rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testMultiplyInt(long a, long b, long c, int n) {
+ assertMultiplyInt(a, b, c, n);
+ assertMultiplyInt(a >>> 32, b, c, n);
+ assertMultiplyInt(0, b, c, n);
+ }
+
+ private static void assertMultiplyInt(long a, long b, long c, int n) {
+ final UInt192 v = new UInt192(a, b, c);
+ BigInteger expected = v.toBigInteger().multiply(BigInteger.valueOf(n & 0xffff_ffffL));
+ // Clip to 192-bits. Only required if the upper 32-bits are non-zero.
+ final int len = expected.bitLength();
+ if (len > 192 && (a >>> Integer.SIZE) != 0) {
+ expected = expected.subtract(expected.shiftRight(192).shiftLeft(192));
+ }
+ Assertions.assertEquals(expected, v.unsignedMultiply(n).toBigInteger());
+ }
+
+ static Stream<Arguments> testMultiplyInt() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final int[] x = {0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ for (final int n : x) {
+ builder.accept(Arguments.of(a, b, c, n));
+ }
+ for (int j = 0; j < 5; j++) {
+ builder.accept(Arguments.of(a, b, c, rng.nextInt()));
+ }
+ }
+ builder.accept(Arguments.of(-1L >>> 32, -1L, -1L, -1));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSubtract(long a, long b, long c, long d, long e) {
+ assertSubtract(a, b, c, d, e);
+ }
+
+ private static void assertSubtract(long a, long b, long c, long d, long e) {
+ final UInt192 x = new UInt192(a, b, c);
+ final UInt128 y = new UInt128(d, e);
+ BigInteger expected = x.toBigInteger().subtract(y.toBigInteger());
+ if (expected.signum() < 0) {
+ expected = expected.add(TWO_POW_192);
+ }
+ Assertions.assertEquals(expected, x.subtract(y).toBigInteger());
+ }
+
+ static Stream<Arguments> testSubtract() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ final long d = rng.nextLong();
+ final long e = rng.nextLong();
+ builder.accept(Arguments.of(a, b, c, d, e));
+ builder.accept(Arguments.of(0, 0, 0, d, e));
+ builder.accept(Arguments.of(-1L, -1L, -1L, d, e));
+ }
+ builder.accept(Arguments.of(-1L, -1L, -1L, -1L, -1L));
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt96Test.java b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt96Test.java
new file mode 100644
index 0000000..f198ab7
--- /dev/null
+++ b/commons-statistics-descriptive/src/test/java/org/apache/commons/statistics/descriptive/UInt96Test.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.statistics.descriptive;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt96}.
+ */
+class UInt96Test {
+ @Test
+ void testCreate() {
+ final UInt96 v = UInt96.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @Test
+ void testAddLongMinValue() {
+ final UInt96 v = UInt96.of(5675757768682342956L);
+ final BigInteger x = BigInteger.ONE.shiftLeft(63);
+ BigInteger expected = v.toBigInteger();
+ for (int i = 1; i <= 5; i++) {
+ // Accepts a negative value without exception. This is
+ // computed correctly if the current low 32 bits
+ // added to the argument do not overflow. This is always
+ // true for min value as all lower 32-bits are zero.
+ v.addPositive(Long.MIN_VALUE);
+ expected = expected.add(x);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final UInt96 v = UInt96.of(a);
+ v.addPositive(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt96 v = UInt96.create();
+ for (final long x : a) {
+ Assertions.assertFalse(x < 0, "Value must be positive");
+ v.addPositive(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, int b, long c, int d) {
+ final UInt96 x = new UInt96(a, b);
+ final UInt96 y = new UInt96(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo32());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 96-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^96).
+ if (expected.testBit(96)) {
+ expected = expected.flipBit(96);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(96)) {
+ expected = expected.flipBit(96);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextInt(), rng.nextLong() >>> 2, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextInt(), rng.nextLong() >>> 1, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextInt(), rng.nextLong() >>> 2, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextInt(), rng.nextLong(), rng.nextInt()));
+ }
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/pom.xml b/commons-statistics-examples/examples-jmh/pom.xml
index f2e9386..ce3319e 100644
--- a/commons-statistics-examples/examples-jmh/pom.xml
+++ b/commons-statistics-examples/examples-jmh/pom.xml
@@ -51,10 +51,14 @@
<artifactId>commons-rng-simple</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-numbers-fraction</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-numbers-rootfinder</artifactId>
- <version>${statistics.commons.numbers.version}</version>
</dependency>
<dependency>
@@ -79,8 +83,14 @@
<!-- Workaround to avoid duplicating config files. -->
<statistics.parent.dir>${basedir}/../..</statistics.parent.dir>
+ <!-- JDK 11+ required for Math.multiplyHigh -->
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+ <maven.compiler.release>11</maven.compiler.release>
+ <commons.compiler.release>11</commons.compiler.release>
+
<!-- JMH Benchmark related properties: version, name of the benchmarking uber jar. -->
- <jmh.version>1.33</jmh.version>
+ <jmh.version>1.36</jmh.version>
<uberjar.name>examples-jmh</uberjar.name>
<project.mainClass>org.openjdk.jmh.Main</project.mainClass>
<!-- Disable analysis for benchmarking code. -->
@@ -88,6 +98,17 @@
<spotbugs.skip>true</spotbugs.skip>
<!-- Disable JDK compatibility check for benchmarking code. -->
<animal.sniffer.skip>true</animal.sniffer.skip>
+
+ <!--
+ NOTE:
+ This module uses Java 11 but does not have an explicit module-info for imported packages.
+ A module-info is generated by the moditect plugin. This does not appear to be discovered
+ as it is in the build output directory.
+ This can cause the javadoc plugin to fail when run with the package phase so skip to make
+ this module compatible with the default goal. Run javadoc plugin using e.g.:
+ mvn javadoc:javadoc -Dmaven.javadoc.skip=false
+ -->
+ <maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
<build>
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128.java
new file mode 100644
index 0000000..66c9e92
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128.java
@@ -0,0 +1,276 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import org.apache.commons.numbers.core.DD;
+
+/**
+ * A mutable 128-bit signed integer.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.Int128} to allow benchmarking.
+ * Additional methods may have been added for comparative benchmarks.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values.
+ *
+ * <p>Note: This number uses a signed long integer representation of:
+ *
+ * <pre>value = 2<sup>64</sup> * hi64 + lo64</pre>
+ *
+ * <p>If the high value is zero then the low value is the long representation of the
+ * number including the sign bit. Otherwise the low value corresponds to a correction
+ * term for the scaled high value which contains the sign-bit of the number.
+ *
+ * @since 1.1
+ */
+final class Int128 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ /** low 64-bits. */
+ private long lo;
+ /** high 64-bits. */
+ private long hi;
+
+ /**
+ * Create an instance.
+ */
+ private Int128() {
+ // No-op
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param x Value.
+ */
+ private Int128(long x) {
+ lo = x;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ */
+ Int128(long hi, long lo) {
+ this.lo = lo;
+ this.hi = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static Int128 create() {
+ return new Int128();
+ }
+
+ /**
+ * Create an instance of the {@code long} value.
+ *
+ * @param x Value.
+ * @return the instance
+ */
+ static Int128 of(long x) {
+ return new Int128(x);
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(long x) {
+ final long y = lo;
+ final long r = y + x;
+ // Overflow if the result has the opposite sign of both arguments
+ // (+,+) -> -
+ // (-,-) -> +
+ // Detect opposite sign:
+ if (((y ^ r) & (x ^ r)) < 0) {
+ // Carry overflow bit
+ hi += x < 0 ? -1 : 1;
+ }
+ lo = r;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add2(long x) {
+ final long y = lo;
+ final long r = y + x;
+ // Overflow if the result has the opposite sign of both arguments
+ // (+,+) -> -
+ // (-,-) -> +
+ // Branchless.
+ // Extract sign bit.
+ long signMask = ((y ^ r) & (x ^ r)) >> 63;
+ // Carry using [0/1] * [+1/-1]
+ hi += signMask & (1 - ((x >>> 62) & 0x2));
+ lo = r;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(Int128 x) {
+ // Avoid issues adding to itself
+ final long l = x.lo;
+ final long h = x.hi;
+ add(l);
+ hi += h;
+ }
+
+ /**
+ * Compute the square of the low 64-bits of this number.
+ *
+ * <p>Warning: This ignores the upper 64-bits. Use with caution.
+ *
+ * @return the square
+ */
+ UInt128 squareLow() {
+ final long x = lo;
+ final long upper = IntMath.squareHigh(x);
+ return new UInt128(upper, x * x);
+ }
+
+ /**
+ * Compute the square of this number.
+ *
+ * @return the square
+ */
+ BigInteger square() {
+ if (hi == 0) {
+ final long x = lo;
+ final long upper = IntMath.squareHigh(x);
+ final long lower = x * x;
+ if (upper == 0) {
+ return BigInteger.valueOf(lower);
+ }
+ return new BigInteger(1, ByteBuffer.allocate(Long.BYTES * 2)
+ .putLong(upper).putLong(lower).array());
+ }
+ final BigInteger result = toBigInteger();
+ return result.multiply(result);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ long h = hi;
+ long l = lo;
+ // Special cases
+ if (h == 0) {
+ return BigInteger.valueOf(l);
+ }
+ if (l == 0) {
+ return BigInteger.valueOf(h).shiftLeft(64);
+ }
+
+ // The representation is 2^64 * hi64 + lo64.
+ // Here we avoid evaluating the addition:
+ // BigInteger.valueOf(l).add(BigInteger.valueOf(h).shiftLeft(64))
+ // It is faster to create from bytes.
+ // BigInteger bytes are an unsigned integer in BigEndian format, plus a sign.
+ // If both values are positive we can use the values unchanged.
+ // Otherwise selective negation is used to create a positive magnitude
+ // and we track the sign.
+ // Note: Negation of -2^63 is valid to create an unsigned 2^63.
+
+ int sign = 1;
+ if ((h ^ l) < 0) {
+ // Opposite signs and lo64 is not zero.
+ // The lo64 bits are an adjustment to the magnitude of hi64
+ // to make it smaller.
+ // Here we rearrange to [2^64 * (hi64-1)] + [2^64 - lo64].
+ // The second term [2^64 - lo64] can use lo64 as an unsigned 64-bit integer.
+ // The first term [2^64 * (hi64-1)] does not work if low is zero.
+ // It would work if zero was detected and we carried the overflow
+ // bit up to h to make it equal to: (h - 1) + 1 == h.
+ // Instead lo64 == 0 is handled as a special case above.
+
+ if (h >= 0) {
+ // Treat (unchanged) low as an unsigned add
+ h = h - 1;
+ } else {
+ // As above with negation
+ h = ~h; // -h - 1
+ l = -l;
+ sign = -1;
+ }
+ } else if (h < 0) {
+ // Invert negative values to create the equivalent positive magnitude.
+ h = -h;
+ l = -l;
+ sign = -1;
+ }
+
+ return new BigInteger(sign,
+ ByteBuffer.allocate(Long.BYTES * 2)
+ .putLong(h).putLong(l).array());
+ }
+
+ /**
+ * Convert to a double-double.
+ *
+ * @return the value
+ */
+ DD toDD() {
+ // Don't combine two 64-bit DD numbers:
+ // DD.of(hi).scalb(64).add(DD.of(lo))
+ // It is more accurate to create a 96-bit number and add the final 32-bits.
+ // Sum low to high.
+ return DD.of(lo).add((hi & MASK32) * 0x1.0p64).add((hi >> Integer.SIZE) * 0x1.0p96);
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * <p>If the high value is zero then the low value is the long representation of the
+ * number including the sign bit. Otherwise this value corresponds to a correction
+ * term for the scaled high value which contains the sign-bit of the number
+ * (see {@link Int128}).
+ *
+ * @return the low 64-bits
+ */
+ long lo64() {
+ return lo;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return the high 64-bits
+ * @see #lo64()
+ */
+ long hi64() {
+ return hi;
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMath.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMath.java
new file mode 100644
index 0000000..4c9292e
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMath.java
@@ -0,0 +1,309 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/**
+ * Support class for integer math.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.IntMath} to allow benchmarking.
+ * Additional methods may have been added for comparative benchmarks.
+ *
+ * @since 1.1
+ */
+final class IntMath {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+ /** Mask for the lower 52-bits of a long. */
+ private static final long MASK52 = 0xf_ffff_ffff_ffffL;
+
+ /** No instances. */
+ private IntMath() {}
+
+ /**
+ * Square the values as if an unsigned 64-bit long to produce the high 64-bits
+ * of the 128-bit unsigned result.
+ *
+ * <p>This method computes the equivalent of:
+ * <pre>{@code
+ * Math.multiplyHigh(x, x)
+ * Math.unsignedMultiplyHigh(x, x) - (((x >> 63) & x) << 1)
+ * }</pre>
+ *
+ * <p>Note: The method {@code Math.multiplyHigh} was added in JDK 9
+ * and should be used as above when the source code targets Java 11
+ * to exploit the intrinsic method.
+ *
+ * <p>Note: The method uses the unsigned multiplication. When the input is negative
+ * it can be adjusted to the signed result by subtracting the argument twice from the
+ * result.
+ *
+ * @param x Value
+ * @return the high 64-bits of the 128-bit result
+ */
+ static long squareHigh(long x) {
+ // Computation is based on the following observation about the upper (a and x)
+ // and lower (b and y) bits of unsigned big-endian integers:
+ // ab * xy
+ // = b * y
+ // + b * x0
+ // + a0 * y
+ // + a0 * x0
+ // = b * y
+ // + b * x * 2^32
+ // + a * y * 2^32
+ // + a * x * 2^64
+ //
+ // Summation using a character for each byte:
+ //
+ // byby byby
+ // + bxbx bxbx 0000
+ // + ayay ayay 0000
+ // + axax axax 0000 0000
+ //
+ // The summation can be rearranged to ensure no overflow given
+ // that the result of two unsigned 32-bit integers multiplied together
+ // plus two full 32-bit integers cannot overflow 64 bits:
+ // > long x = (1L << 32) - 1
+ // > x * x + x + x == -1 (all bits set, no overflow)
+ //
+ // The carry is a composed intermediate which will never overflow:
+ //
+ // byby byby
+ // + bxbx 0000
+ // + ayay ayay 0000
+ //
+ // + bxbx 0000 0000
+ // + axax axax 0000 0000
+
+ final long a = x >>> 32;
+ final long b = x & MASK32;
+
+ final long aa = a * a;
+ final long ab = a * b;
+ final long bb = b * b;
+
+ // Cannot overflow
+ final long carry = (bb >>> 32) +
+ (ab & MASK32) +
+ ab;
+ // Note:
+ // low = (carry << 32) | (bb & MASK32)
+ // Benchmarking shows outputting low to a long[] output argument
+ // has no benefit over computing 'low = value * value' separately.
+
+ final long hi = (ab >>> 32) + (carry >>> 32) + aa;
+ // Adjust to the signed result:
+ // if x < 0:
+ // hi - 2 * x
+ return hi - (((x >> 63) & x) << 1);
+ }
+
+ /**
+ * Multiply the two values as if unsigned 64-bit longs to produce the high 64-bits
+ * of the 128-bit unsigned result.
+ *
+ * <p>This method computes the equivalent of:
+ * <pre>{@code
+ * Math.multiplyHigh(a, b) + ((a >> 63) & b) + ((b >> 63) & a)
+ * }</pre>
+ *
+ * <p>Note: The method {@code Math.multiplyHigh} was added in JDK 9
+ * and should be used as above when the source code targets Java 11
+ * to exploit the intrinsic method.
+ *
+ * <p>Note: The method {@code Math.unsignedMultiplyHigh} was added in JDK 18
+ * and should be used when the source code target allows.
+ *
+ * <p>Taken from {@code o.a.c.rng.core.source64.LXMSupport}.
+ *
+ * @param value1 the first value
+ * @param value2 the second value
+ * @return the high 64-bits of the 128-bit result
+ */
+ static long unsignedMultiplyHigh(long value1, long value2) {
+ // Computation is based on the following observation about the upper (a and x)
+ // and lower (b and y) bits of unsigned big-endian integers:
+ // ab * xy
+ // = b * y
+ // + b * x0
+ // + a0 * y
+ // + a0 * x0
+ // = b * y
+ // + b * x * 2^32
+ // + a * y * 2^32
+ // + a * x * 2^64
+ //
+ // Summation using a character for each byte:
+ //
+ // byby byby
+ // + bxbx bxbx 0000
+ // + ayay ayay 0000
+ // + axax axax 0000 0000
+ //
+ // The summation can be rearranged to ensure no overflow given
+ // that the result of two unsigned 32-bit integers multiplied together
+ // plus two full 32-bit integers cannot overflow 64 bits:
+ // > long x = (1L << 32) - 1
+ // > x * x + x + x == -1 (all bits set, no overflow)
+ //
+ // The carry is a composed intermediate which will never overflow:
+ //
+ // byby byby
+ // + bxbx 0000
+ // + ayay ayay 0000
+ //
+ // + bxbx 0000 0000
+ // + axax axax 0000 0000
+
+ final long a = value1 >>> 32;
+ final long b = value1 & MASK32;
+ final long x = value2 >>> 32;
+ final long y = value2 & MASK32;
+
+ final long by = b * y;
+ final long bx = b * x;
+ final long ay = a * y;
+ final long ax = a * x;
+
+ // Cannot overflow
+ final long carry = (by >>> 32) +
+ (bx & MASK32) +
+ ay;
+ // Note:
+ // low = (carry << 32) | (by & INT_TO_UNSIGNED_BYTE_MASK)
+ // Benchmarking shows outputting low to a long[] output argument
+ // has no benefit over computing 'low = value1 * value2' separately.
+
+ return (bx >>> 32) + (carry >>> 32) + ax;
+ }
+
+ /**
+ * Multiply the arguments as if unsigned integers to a {@code double} result.
+ *
+ * @param a Value.
+ * @param b Value.
+ * @return the double
+ */
+ static double unsignedMultiplyToDoubleBigInteger(long a, long b) {
+ final long lo = a * b;
+
+ // Fast case: check the arguments cannot overflow a long.
+ // This is true if neither has the upper 33-bits set.
+ if (((a | b) >>> 31) == 0) {
+ // Implicit conversion to a double
+ return lo;
+ }
+
+ final long hi = unsignedMultiplyHigh(a, b);
+
+ // Convert to a double using BigInteger
+ return new BigInteger(1, ByteBuffer.allocate(Long.BYTES * 2)
+ .putLong(hi)
+ .putLong(lo)
+ .array()).doubleValue();
+ }
+
+ /**
+ * Multiply the arguments as if unsigned integers to a {@code double} result.
+ *
+ * @param x Value.
+ * @param y Value.
+ * @return the double
+ */
+ static double unsignedMultiplyToDouble(long x, long y) {
+ final long lo = x * y;
+ // Fast case: check the arguments cannot overflow a long.
+ // This is true if neither has the upper 33-bits set.
+ if (((x | y) >>> 31) == 0) {
+ // Implicit conversion to a double.
+ return lo;
+ }
+ return uin128ToDouble(unsignedMultiplyHigh(x, y), lo);
+ }
+
+ /**
+ * Convert an unsigned 128-bit integer to a {@code double}.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ * @return the double
+ */
+ static double uin128ToDouble(long hi, long lo) {
+ // Require the representation:
+ // 2^exp * mantissa / 2^53
+ // The mantissa has an implied leading 1-bit.
+
+ // We have the mantissa final bit as xxx0 or xxx1.
+ // To perform correct rounding we maintain the 54-th bit (a) and
+ // a check bit (b) of remaining bits.
+ // Cases:
+ // xxx0 00 - round-down [1]
+ // xxx0 0b - round-down [1]
+ // xxx0 a0 - half-even, round-down [4]
+ // xxx0 ab - round-up [2]
+ // xxx1 00 - round-down [1]
+ // xxx1 0b - round-down [1]
+ // xxx1 a0 - half-even, round-up [3]
+ // xxx1 ab - round-up [2]
+ // [1] If the 54-th bit is 0 always round-down.
+ // [2] Otherwise round-up if the check bit is set or
+ // [3] the final bit is odd (half-even rounding up).
+ // [4] half-even rounding down.
+
+ if (hi == 0) {
+ // If lo is a 63-bit result then we are done
+ if (lo >= 0) {
+ return lo;
+ }
+ // Create a 63-bit number with a sticky bit for rounding, rescale the result
+ return 2 * (double) ((lo >>> 1) | (lo & 0x1));
+ }
+
+ // Initially we create the most significant 64-bits.
+ final int shift = Long.numberOfLeadingZeros(hi);
+ // Shift the high bits and add trailing low bits.
+ // The mask is for the bits from low that are *not* used.
+ // Flipping the mask obtains the bits we concatenate
+ // after shifting (64 - shift).
+ final long maskLow = -1L >>> shift;
+ long bits64 = (hi << shift) | ((lo & ~maskLow) >>> -shift);
+ // exponent for 2^exp is the index of the highest bit in the 128 bit integer
+ final int exp = 127 - shift;
+ // Some of the low bits are lost. If non-zero set
+ // a sticky bit for rounding.
+ bits64 |= (lo & maskLow) == 0 ? 0 : 1;
+
+ // We have a 64-bit unsigned fraction magnitude and an exponent.
+ // This must be converted to a IEEE double by mapping the fraction to a base of 2^53.
+
+ // Create the 53-bit mantissa without the implicit 1-bit
+ long bits = (bits64 >>> 11) & MASK52;
+ // Extract 54-th bit and a sticky bit
+ final long a = (bits64 >>> 10) & 0x1;
+ final long b = (bits64 << 54) == 0 ? 0 : 1;
+ // Perform half-even rounding.
+ bits += a & (b | (bits & 0x1));
+ // Add the exponent.
+ // No worry about overflow to the sign bit as the max exponent is 127.
+ bits += (long) (exp + 1023) << 52;
+
+ return Double.longBitsToDouble(bits);
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMomentPerformance.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMomentPerformance.java
new file mode 100644
index 0000000..cbc4af6
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMomentPerformance.java
@@ -0,0 +1,1719 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Arrays;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.function.DoubleConsumer;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntConsumer;
+import java.util.function.LongConsumer;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToLongFunction;
+import java.util.stream.LongStream;
+import org.apache.commons.numbers.core.DD;
+import org.apache.commons.numbers.fraction.BigFraction;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.apache.commons.statistics.descriptive.DoubleStatistic;
+import org.apache.commons.statistics.descriptive.IntMean;
+import org.apache.commons.statistics.descriptive.IntStatistic;
+import org.apache.commons.statistics.descriptive.IntVariance;
+import org.apache.commons.statistics.descriptive.LongMean;
+import org.apache.commons.statistics.descriptive.LongStatistic;
+import org.apache.commons.statistics.descriptive.LongVariance;
+import org.apache.commons.statistics.descriptive.Mean;
+import org.apache.commons.statistics.descriptive.Variance;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Executes a benchmark of the moment-based statistics for integer values
+ * ({@code int} or {@code long}) compared to using {@code double} values.
+ */
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Benchmark)
+@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
+public class IntMomentPerformance {
+ /** Commons Statistics Mean implementation. */
+ private static final String DOUBLE_MEAN = "DoubleMean";
+ /** Integer mean implementation. */
+ private static final String INT_MEAN = "IntMean";
+ /** Long mean implementation. */
+ private static final String LONG_MEAN = "LongMean";
+ /** Sum using a long mean implementation. */
+ private static final String LONG_SUM_MEAN = "LongSumMean";
+ /** Sum using a BigInteger mean implementation. */
+ private static final String BIG_INTEGER_SUM_MEAN = "BigIntegerSumMean";
+ /** JDK Stream mean implementation. */
+ private static final String STREAM_MEAN = "StreamMean";
+ /** Commons Statistics Variance implementation. */
+ private static final String DOUBLE_VAR = "DoubleVariance";
+ /** Integer variance implementation. */
+ private static final String INT_VAR = "IntVariance";
+ /** Long variance implementation. */
+ private static final String LONG_VAR = "LongVariance";
+ /** Long variance implementation using Math.multiplyHigh. */
+ private static final String LONG_VAR2 = "LongVariance2";
+
+ /**
+ * Source of array data.
+ */
+ @State(Scope.Benchmark)
+ public static class DataSource {
+ /** Data length. */
+ @Param({"2", "1000"})
+ private int length;
+
+ /** Data. */
+ private int[] data;
+
+ /** Data as a double. */
+ private double[] doubleData;
+
+ /** Data as a long. */
+ private long[] longData;
+
+ /**
+ * @return the data
+ */
+ public int[] getData() {
+ return data;
+ }
+
+ /**
+ * @return the data
+ */
+ public double[] getDoubleData() {
+ return doubleData;
+ }
+
+ /**
+ * @return the data
+ */
+ public long[] getLongData() {
+ return longData;
+ }
+
+ /**
+ * Create the data.
+ * Data will be randomized per iteration.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ longData = RandomSource.XO_RO_SHI_RO_128_PP.create().longs(length).toArray();
+ doubleData = Arrays.stream(longData).asDoubleStream().toArray();
+ data = Arrays.stream(longData).mapToInt(x -> (int) x).toArray();
+ }
+ }
+
+ /**
+ * Source of a {@link IntConsumer} action.
+ */
+ @State(Scope.Benchmark)
+ public static class IntActionSource {
+ /** Name of the source. */
+ @Param({DOUBLE_MEAN, INT_MEAN,
+ // Disabled: Run-time ~ IntMean
+ // LONG_SUM_MEAN
+ DOUBLE_VAR, INT_VAR})
+ private String name;
+
+ /** The action. */
+ private Supplier<IntStatistic> action;
+
+ /**
+ * @return the action
+ */
+ public IntStatistic getAction() {
+ return action.get();
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (DOUBLE_MEAN.equals(name)) {
+ action = () -> {
+ final Mean m = Mean.create();
+ return createIntStatistic(m, m);
+ };
+ } else if (INT_MEAN.equals(name)) {
+ action = () -> {
+ final IntMean m = IntMean.create();
+ return createIntStatistic(m, m);
+ };
+ } else if (LONG_SUM_MEAN.equals(name)) {
+ action = () -> {
+ final LongSumMean m = new LongSumMean();
+ return createIntStatistic(m, m);
+ };
+ } else if (DOUBLE_VAR.equals(name)) {
+ action = () -> {
+ final Variance m = Variance.create();
+ return createIntStatistic(m, m);
+ };
+ } else if (INT_VAR.equals(name)) {
+ action = () -> {
+ final IntVariance m = IntVariance.create();
+ return createIntStatistic(m, m);
+ };
+ } else {
+ throw new IllegalStateException("Unknown int action: " + name);
+ }
+ }
+
+ /**
+ * Creates the {@link IntStatistic}.
+ *
+ * @param c Consumer.
+ * @param s Supplier.
+ * @return the statistic
+ */
+ private static IntStatistic createIntStatistic(IntConsumer c, DoubleSupplier s) {
+ return new IntStatistic() {
+ @Override
+ public void accept(int value) {
+ c.accept(value);
+ }
+ @Override
+ public double getAsDouble() {
+ return s.getAsDouble();
+ }
+ };
+ }
+
+ /**
+ * Creates the {@link IntStatistic}.
+ *
+ * @param c Consumer.
+ * @param s Supplier.
+ * @return the statistic
+ */
+ private static IntStatistic createIntStatistic(DoubleConsumer c, DoubleSupplier s) {
+ return new IntStatistic() {
+ @Override
+ public void accept(int value) {
+ c.accept(value);
+ }
+ @Override
+ public double getAsDouble() {
+ return s.getAsDouble();
+ }
+ };
+ }
+ }
+
+ /**
+ * Source of a {@link DoubleConsumer} action.
+ */
+ @State(Scope.Benchmark)
+ public static class DoubleActionSource {
+ /** Name of the source. */
+ @Param({DOUBLE_MEAN, DOUBLE_VAR})
+ private String name;
+
+ /** The action. */
+ private Supplier<DoubleStatistic> action;
+
+ /**
+ * @return the action
+ */
+ public DoubleStatistic getAction() {
+ return action.get();
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (DOUBLE_MEAN.equals(name)) {
+ action = () -> {
+ final Mean m = Mean.create();
+ return createDoubleStatistic(m, m);
+ };
+ } else if (DOUBLE_VAR.equals(name)) {
+ action = () -> {
+ final Variance m = Variance.create();
+ return createDoubleStatistic(m, m);
+ };
+ } else {
+ throw new IllegalStateException("Unknown double action: " + name);
+ }
+ }
+
+ /**
+ * Creates the {@link DoubleStatistic}.
+ *
+ * <p>This method is here to provide parity when comparing actual instances
+ * of {@link DoubleStatistic} with composed objects for the equivalent
+ * int/long statistics.
+ *
+ * @param c Consumer.
+ * @param s Supplier.
+ * @return the statistic
+ */
+ private static DoubleStatistic createDoubleStatistic(DoubleConsumer c, DoubleSupplier s) {
+ return new DoubleStatistic() {
+ @Override
+ public void accept(double value) {
+ c.accept(value);
+ }
+ @Override
+ public double getAsDouble() {
+ return s.getAsDouble();
+ }
+ };
+ }
+ }
+
+ /**
+ * Source of a {@link LongConsumer} action.
+ */
+ @State(Scope.Benchmark)
+ public static class LongActionSource {
+ /** Name of the source. */
+ @Param({DOUBLE_MEAN, LONG_MEAN, BIG_INTEGER_SUM_MEAN,
+ DOUBLE_VAR, LONG_VAR, LONG_VAR2})
+ private String name;
+
+ /** The action. */
+ private Supplier<LongStatistic> action;
+
+ /**
+ * @return the action
+ */
+ public LongStatistic getAction() {
+ return action.get();
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (DOUBLE_MEAN.equals(name)) {
+ action = () -> {
+ final Mean m = Mean.create();
+ return createLongStatistic(m, m);
+ };
+ } else if (LONG_MEAN.equals(name)) {
+ action = () -> {
+ final LongMean m = LongMean.create();
+ return createLongStatistic(m, m);
+ };
+ } else if (BIG_INTEGER_SUM_MEAN.equals(name)) {
+ action = () -> {
+ final BigIntegerSumMean m = new BigIntegerSumMean();
+ return createLongStatistic(m, m);
+ };
+ } else if (DOUBLE_VAR.equals(name)) {
+ action = () -> {
+ final Variance m = Variance.create();
+ return createLongStatistic(m, m);
+ };
+ } else if (LONG_VAR.equals(name)) {
+ action = () -> {
+ final LongVariance m = LongVariance.create();
+ return createLongStatistic(m, m);
+ };
+ } else if (LONG_VAR2.equals(name)) {
+ action = () -> {
+ final LongVariance2 m = LongVariance2.create();
+ return createLongStatistic(m, m);
+ };
+ } else {
+ throw new IllegalStateException("Unknown long action: " + name);
+ }
+ }
+
+ /**
+ * Creates the {@link LongStatistic}.
+ *
+ * @param c Consumer.
+ * @param s Supplier.
+ * @return the statistic
+ */
+ private static LongStatistic createLongStatistic(LongConsumer c, DoubleSupplier s) {
+ return new LongStatistic() {
+ @Override
+ public void accept(long value) {
+ c.accept(value);
+ }
+ @Override
+ public double getAsDouble() {
+ return s.getAsDouble();
+ }
+ };
+ }
+
+ /**
+ * Creates the {@link LongStatistic}.
+ *
+ * @param c Consumer.
+ * @param s Supplier.
+ * @return the statistic
+ */
+ private static LongStatistic createLongStatistic(DoubleConsumer c, DoubleSupplier s) {
+ return new LongStatistic() {
+ @Override
+ public void accept(long value) {
+ c.accept(value);
+ }
+ @Override
+ public double getAsDouble() {
+ return s.getAsDouble();
+ }
+ };
+ }
+ }
+
+ /**
+ * Source of a {@link ToDoubleFunction} for a {@code int[]}.
+ */
+ @State(Scope.Benchmark)
+ public static class IntFunctionSource {
+ /** Name of the source. */
+ @Param({INT_MEAN,
+ // Disabled: Run-time ~ IntMean
+ //LONG_SUM_MEAN,
+ STREAM_MEAN, INT_VAR})
+ private String name;
+
+ /** The action. */
+ private ToDoubleFunction<int[]> function;
+
+ /**
+ * @return the function
+ */
+ public ToDoubleFunction<int[]> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (INT_MEAN.equals(name)) {
+ function = x -> IntMean.of(x).getAsDouble();
+ } else if (LONG_SUM_MEAN.equals(name)) {
+ function = LongSumMean::mean;
+ } else if (STREAM_MEAN.equals(name)) {
+ function = x -> Arrays.stream(x).average().orElse(Double.NaN);
+ } else if (INT_VAR.equals(name)) {
+ function = x -> IntVariance.of(x).getAsDouble();
+ } else {
+ throw new IllegalStateException("Unknown int function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Source of a {@link ToDoubleFunction} for a {@code double[]}.
+ */
+ @State(Scope.Benchmark)
+ public static class DoubleFunctionSource {
+ /** Name of the source. */
+ @Param({DOUBLE_MEAN, DOUBLE_VAR})
+ private String name;
+
+ /** The action. */
+ private ToDoubleFunction<double[]> function;
+
+ /**
+ * @return the function
+ */
+ public ToDoubleFunction<double[]> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (DOUBLE_MEAN.equals(name)) {
+ function = x -> Mean.of(x).getAsDouble();
+ } else if (DOUBLE_VAR.equals(name)) {
+ function = x -> Variance.of(x).getAsDouble();
+ } else {
+ throw new IllegalStateException("Unknown double function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Source of a {@link ToDoubleFunction} for a {@code long[]}.
+ */
+ @State(Scope.Benchmark)
+ public static class LongFunctionSource {
+ /** Name of the source. */
+ @Param({LONG_MEAN, BIG_INTEGER_SUM_MEAN, LONG_VAR, LONG_VAR2})
+ private String name;
+
+ /** The action. */
+ private ToDoubleFunction<long[]> function;
+
+ /**
+ * @return the function
+ */
+ public ToDoubleFunction<long[]> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if (LONG_MEAN.equals(name)) {
+ function = x -> LongMean.of(x).getAsDouble();
+ } else if (BIG_INTEGER_SUM_MEAN.equals(name)) {
+ function = BigIntegerSumMean::mean;
+ } else if (LONG_VAR.equals(name)) {
+ function = x -> LongVariance.of(x).getAsDouble();
+ } else if (LONG_VAR2.equals(name)) {
+ function = x -> LongVariance2.of(x).getAsDouble();
+ } else {
+ throw new IllegalStateException("Unknown long function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Class containing the variance data.
+ */
+ static class IntVarianceData {
+ /** Sum of the squared values. */
+ private final UInt128 sumSq;
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /**
+ * @param sumSq Sum of the squared values.
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ IntVarianceData(UInt128 sumSq, Int128 sum, long n) {
+ this.sumSq = sumSq;
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * @return the sum of the squared values
+ */
+ UInt128 getSumSq() {
+ return new UInt128(sumSq.hi64(), sumSq.lo64());
+ }
+
+ /**
+ * @return the sum
+ */
+ Int128 getSum() {
+ return new Int128(sum.hi64(), sum.lo64());
+ }
+
+ /**
+ * @return the count of values that have been added
+ */
+ long getN() {
+ return n;
+ }
+
+ /**
+ * @return the copy
+ */
+ IntVarianceData copy() {
+ return new IntVarianceData(getSumSq(), getSum(), n);
+ }
+
+ /**
+ * Adds the other instance.
+ *
+ * @param other the other
+ * @return this instance
+ */
+ IntVarianceData add(IntVarianceData other) {
+ // Prevent the data from becoming too large
+ n = Math.addExact(n, other.n);
+ sumSq.add(other.sumSq);
+ sum.add(other.sum);
+ return this;
+ }
+ }
+
+ /**
+ * Source of {@code int} variance data.
+ *
+ * <p>This class generates a pool of variance data from a random sample of integers
+ * in a range. The pool objects are then combined with each other for a given number of
+ * rounds, effectively doubling the size of pool objects each round.
+ * Using the defaults will create objects in the pool of:
+ * <pre>
+ * E[ sum(x) ] = (511 / 2) mean value * (95 / 2) mean samples ~ 12136.25 ~ 2^8 * 2^5.5 {@code < 2^14}
+ * E[ sum(x^2) = ((511 / 2)^2 mean value^2) * (95 / 2) mean samples ~ 3100811.875 ~ 2^16 * 2^5.5 {@code < 2^22}
+ * Max[ sum(x) = 511 * 63 = 32193 {@code < 2^15}
+ * Max[ sum(x^2) = 15^2 * 63 = 14175 {@code < 2^16}
+ * man[ n ] = 63 {@code < 2^6}
+ * </pre>
+ * <p>The objects from this pool can be added together a maximum of 56 times before n overflows.
+ * The sum of the values will overflow a long at approximately 18 combines.
+ */
+ @State(Scope.Benchmark)
+ public static class IntVarianceDataSource {
+ /** Consistent seed. */
+ private static final Long SEED = ThreadLocalRandom.current().nextLong();
+ /** Lower limit. */
+ @Param({"0"})
+ private int origin;
+ /** Upper limit. */
+ @Param({"512"})
+ private int bound;
+ /** Minimum samples. */
+ @Param({"32"})
+ private int minSamples;
+ /** Maximum samples. */
+ @Param({"64"})
+ private int maxSamples;
+ /** Pool size. */
+ @Param({"64"})
+ private int poolSize;
+ /** Number of combine operations. */
+ @Param({"8", "16", "24", "32", "48"})
+ private int combine;
+
+ /** Data. */
+ private IntVarianceData[] data;
+
+ /**
+ * The number of data values.
+ *
+ * @return the size
+ */
+ public int size() {
+ return data.length;
+ }
+ /**
+ * Get a copy of the data for the specified index.
+ *
+ * @param i Index.
+ * @return the data
+ */
+ public IntVarianceData getData(int i) {
+ return data[i].copy();
+ }
+
+ /**
+ * Create the data.
+ */
+ @Setup
+ public void setup() {
+ // Consistent seed so the same data is provided to all methods
+ final UniformRandomProvider rng = RandomSource.XO_SHI_RO_512_SS.create(SEED);
+ // Initial pool
+ final IntVarianceData[] pool = new IntVarianceData[poolSize];
+ for (int i = 0; i < pool.length; i++) {
+ final int n = rng.nextInt(minSamples, maxSamples);
+ final UInt128 sumSq = UInt128.create();
+ final Int128 sum = Int128.create();
+ rng.ints(n, origin, bound).forEach(x -> {
+ sumSq.addPositive((long) x * x);
+ sum.add(x);
+ });
+ pool[i] = new IntVarianceData(sumSq, sum, n);
+ }
+ // Combine to grow the average size of the pool objects
+ for (int round = 0; round < combine; round++) {
+ final IntVarianceData[] last = pool.clone();
+ for (int i = 0; i < pool.length; i++) {
+ // Copy the instance that will be the LHS of the add operation
+ pool[i] = last[i].copy().add(last[rng.nextInt(poolSize)]);
+ }
+ }
+ data = pool;
+ }
+ }
+
+ /**
+ * Source of a {@link ToDoubleFunction} for a {@code IntVarianceData}.
+ */
+ @State(Scope.Benchmark)
+ public static class IntVarianceFunctionSource {
+ /** {@link MathContext} with 20 digits of precision. */
+ private static final MathContext MC_20_DIGITS = new MathContext(20);
+
+ /** Name of the source. */
+ @Param({"DD", "DD2", "BigIntegerPow", "BigIntegerMultiply",
+ "SumSquareBigInteger", "SumSquareMultiplyBigInteger",
+ "UIntBigInteger", "UIntDD", "UIntDD2", "UIntBigInteger2", "UIntBigInteger3",
+ "UIntDouble",
+ // Very slow
+ //"UIntBigFraction", "UIntBigDecimal"
+ })
+ private String name;
+
+ /** The action. */
+ private ToDoubleFunction<IntVarianceData> function;
+
+ /**
+ * @return the function
+ */
+ public ToDoubleFunction<IntVarianceData> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if ("DD".equals(name)) {
+ function = IntVarianceFunctionSource::varianceDD;
+ } else if ("DD2".equals(name)) {
+ function = IntVarianceFunctionSource::varianceDD2;
+ } else if ("BigIntegerPow".equals(name)) {
+ function = IntVarianceFunctionSource::varianceBigIntegerPow;
+ } else if ("BigIntegerMultiply".equals(name)) {
+ function = IntVarianceFunctionSource::varianceBigIntegerMultiply;
+ } else if ("SumSquareBigInteger".equals(name)) {
+ function = IntVarianceFunctionSource::varianceSumSquareBigInteger;
+ } else if ("SumSquareMultiplyBigInteger".equals(name)) {
+ function = IntVarianceFunctionSource::varianceSumSquareMultiplyIntBigInteger;
+ } else if ("UIntBigInteger".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntBigInteger;
+ } else if ("UIntDD".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntDD;
+ } else if ("UIntDD2".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntDD2;
+ } else if ("UIntBigInteger2".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntBigInteger2;
+ } else if ("UIntBigInteger3".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntBigInteger3;
+ } else if ("UIntDouble".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntDouble;
+ } else if ("UIntBigFraction".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntBigFraction;
+ } else if ("UIntBigDecimal".equals(name)) {
+ function = IntVarianceFunctionSource::varianceUIntBigDecimal;
+ } else {
+ throw new IllegalStateException("Unknown int variance function: " + name);
+ }
+ }
+
+ /**
+ * Convenience method to square a BigInteger.
+ *
+ * @param x Value
+ * @return x^2
+ */
+ private static BigInteger square(BigInteger x) {
+ return x.multiply(x);
+ }
+
+ /**
+ * Compute the variance using double-double arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceDD(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ final DD diff = sumSq.toDD().multiply(n).subtract(sum.toDD().square());
+ if (diff.hi() < 0) {
+ return 0;
+ }
+ // Divisor is an exact double
+ if (n < (1L << 26)) {
+ // n0*n is safe as a long
+ return diff.divide(n0 * n).doubleValue();
+ }
+ return diff.divide(DD.of(n).multiply(DD.of(n0))).doubleValue();
+ }
+
+ /**
+ * Compute the variance using double-double arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceDD2(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations: sum(x^2) - sum(x)^2 / n
+ final DD ss = sumSq.toDD().subtract(sum.toDD().square().divide(n));
+ if (ss.hi() < 0) {
+ return 0;
+ }
+ return ss.divide(n0).doubleValue();
+ }
+
+ /**
+ * Compute the variance using BigInteger arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceBigIntegerPow(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ final BigInteger diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(sum.toBigInteger().pow(2));
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using BigInteger arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceBigIntegerMultiply(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ final BigInteger diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(square(sum.toBigInteger()));
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using Int128 and BigInteger arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceSumSquareBigInteger(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the second term if possible using fast integer arithmetic.
+ final BigInteger term1 = sumSq.toBigInteger().multiply(BigInteger.valueOf(n));
+ final BigInteger term2 = sum.hi64() == 0 ? sum.squareLow().toBigInteger() : square(sum.toBigInteger());
+ final BigInteger diff = term1.subtract(term2);
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using UInt128/Int128 and BigInteger arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceSumSquareMultiplyIntBigInteger(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // sum(x^2) * n will be OK when n < 2^32.
+ final BigInteger term1 = n < 1L << 32 ? sumSq.unsignedMultiply((int) n).toBigInteger() :
+ sumSq.toBigInteger().multiply(BigInteger.valueOf(n));
+ final BigInteger term2 = sum.hi64() == 0 ? sum.squareLow().toBigInteger() : square(sum.toBigInteger());
+ final BigInteger diff = term1.subtract(term2);
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using UInt128/Int128 and BigInteger arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntBigInteger(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ BigInteger diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toBigInteger();
+ } else {
+ // It may still be possible to compute the square
+ BigInteger sum2;
+ if (sum.hi64() == 0) {
+ sum2 = sum.squareLow().toBigInteger();
+ } else {
+ sum2 = sum.toBigInteger();
+ sum2 = sum2.multiply(sum2);
+ }
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(sum2);
+ }
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using UInt128/Int128 and DD arithmetic.
+ * The final divide uses double precision.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntDD(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // Both are safe when n < 2^32.
+ if ((n >>> Integer.SIZE) == 0) {
+ DD diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDD();
+ // Divisor is an exact double
+ if (n < (1L << 26)) {
+ // n0*n is safe as a long
+ return diff.divide(n0 * n).doubleValue();
+ }
+ return diff.divide(DD.of(n).multiply(DD.of(n0))).doubleValue();
+ }
+ BigInteger diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(square(sum.toBigInteger()));
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using UInt128/Int128 and DD arithmetic.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntDD2(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // Both are safe when n < 2^32.
+ if ((n >>> Integer.SIZE) == 0) {
+ DD diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDD();
+ // Divisor is an exact double
+ if (n < (1L << 26)) {
+ // n0*n is safe as a long
+ return diff.divide(n0 * n).doubleValue();
+ }
+ return diff.divide(DD.of(n).multiply(DD.of(n0))).doubleValue();
+ }
+ BigInteger diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(square(sum.toBigInteger()));
+ // Assume n is big to overflow the sum(x)
+ // Compute the divide in double-double precision
+ return DD.of(diff.doubleValue()).divide(DD.of(n).multiply(DD.of(n0))).doubleValue();
+ }
+
+ /**
+ * Compute the variance using unsigned integer (UInt128/Int128 or BigInteger) arithmetic.
+ * The final divide uses double precision.
+ *
+ * <p>Note: This is similar to {@link #varianceUIntBigInteger(IntVarianceData)} but does
+ * not fast compute the squared sum. This benchmarks as faster: the BigInteger multiply
+ * on small values for sum(x)^2 is efficient.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntBigInteger2(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ BigInteger diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toBigInteger();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(square(sum.toBigInteger()));
+ }
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using unsigned integer (UInt128/Int128 or BigInteger) arithmetic.
+ * The final divide uses double precision.
+ *
+ * <p>Note: This is similar to {@link #varianceUIntBigInteger(IntVarianceData)} but does
+ * computes the squared sum in Int128. This benchmarks slower than converting to BigInteger
+ * and computing the square.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntBigInteger3(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ BigInteger diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toBigInteger();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(sum.square());
+ }
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+
+ /**
+ * Compute the variance using unsigned integer (UInt128/Int128 or BigInteger) arithmetic.
+ * The final divide uses double precision.
+ *
+ * <p>Note: This is similar to {@link #varianceUIntBigInteger(IntVarianceData)} but does
+ * not fast compute the squared sum. This benchmarks as faster: the BigInteger multiply
+ * on small values for sum(x)^2 is efficient.
+ *
+ * <p>This method uses the {@link UInt128#toDouble()} to avoid going via BigInteger.
+ * The divisor is computed in extended precision.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntDouble(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ double diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDouble();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(square(sum.toBigInteger())).doubleValue();
+ }
+ // Compute the divide in double precision
+ return diff / IntMath.unsignedMultiplyToDouble(n, n0);
+ }
+
+ /**
+ * Compute the variance using unsigned integer (UInt128/Int128 or BigInteger) arithmetic.
+ * The final divide uses double precision.
+ *
+ * <p>Note: This is similar to {@link #varianceUIntBigInteger(IntVarianceData)} but does
+ * not fast compute the squared sum. This benchmarks as faster: the BigInteger multiply
+ * on small values for sum(x)^2 is efficient.
+ *
+ * <p>The final divide uses BigFraction for large size, or double.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntBigFraction(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ BigInteger diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toBigInteger();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(square(sum.toBigInteger()));
+ }
+ if (n < (1L << 26)) {
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+ return BigFraction.of(diff, BigInteger.valueOf(n0).multiply(BigInteger.valueOf(n)))
+ .doubleValue();
+ }
+
+ /**
+ * Compute the variance using unsigned integer (UInt128/Int128 or BigInteger) arithmetic.
+ * The final divide uses double precision.
+ *
+ * <p>Note: This is similar to {@link #varianceUIntBigInteger(IntVarianceData)} but does
+ * not fast compute the squared sum. This benchmarks as faster: the BigInteger multiply
+ * on small values for sum(x)^2 is efficient.
+ *
+ * <p>The final divide uses BigDecimal for large size, or double.
+ *
+ * @param data Variance data.
+ * @return the variance
+ */
+ static double varianceUIntBigDecimal(IntVarianceData data) {
+ final long n = data.getN();
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final UInt128 sumSq = data.getSumSq();
+ final Int128 sum = data.getSum();
+ // Assume unbiased
+ final long n0 = n - 1;
+ // Extended precision.
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // Compute the term if possible using fast integer arithmetic.
+ // 128-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // Both are safe when n < 2^32.
+ BigInteger diff;
+ if ((n >>> Integer.SIZE) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toBigInteger();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n)).subtract(square(sum.toBigInteger()));
+ }
+ if (n < (1L << 26)) {
+ // Compute the divide in double precision
+ return diff.doubleValue() / ((double) n0 * n);
+ }
+ return new BigDecimal(diff).divide(new BigDecimal(
+ BigInteger.valueOf(n0).multiply(BigInteger.valueOf(n))), MC_20_DIGITS)
+ .doubleValue();
+ }
+ }
+
+ /**
+ * Source of {@code long} array data.
+ * The data is designed to overflow a sum as a long with a specified frequency.
+ * There are 3 cases: positive values; negative values; any sign. The amount
+ * of overflow is controlled using a shift to remove magnitude. No shift expects
+ * overflow 50% of the time when summing same sign values. If both signs are used then the
+ * random walk will be based around 0 with overflow occurring proportional to
+ * the magnitude. Chance of overflow will rapidly drop when the values are not full
+ * magnitude numbers.
+ */
+ @State(Scope.Benchmark)
+ public static class LongDataSource {
+ /** Data length: 2^10. If shift is above 10 then no overflow will occur. */
+ @Param({"1024"})
+ private int length;
+ /** Data sign. */
+ @Param({"positive", "negative", "both"})
+ private String sign;
+ /** Data bit shift. */
+ @Param({"0", "1", "2", "4", "8", "16"})
+ private int shift;
+
+ /** Data. */
+ private long[] data;
+
+ /**
+ * @return the data
+ */
+ public long[] getData() {
+ return data;
+ }
+
+ /**
+ * Create the data.
+ * Data will be randomized per iteration.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ LongStream s = RandomSource.XO_RO_SHI_RO_128_PP.create().longs(length);
+ if ("positive".equals(sign)) {
+ s = s.map(x -> x >>> 1);
+ } else if ("negative".equals(sign)) {
+ s = s.map(x -> x | Long.MIN_VALUE);
+ } else if (!"both".equals(sign)) {
+ throw new IllegalStateException("Unknown sign: " + sign);
+ }
+ if (shift > 0) {
+ final int bits = shift;
+ // Signed shift maintains negative values
+ s = s.map(x -> x >> bits);
+ }
+ data = s.toArray();
+ }
+ }
+
+ /**
+ * Source of a {@link ToLongFunction} for a {@code long[]}.
+ */
+ @State(Scope.Benchmark)
+ public static class LongSumFunctionSource {
+ /** Name of the source.
+ * The branchless 128bitAdd2 runs at constant speed but is slower than 128bitAdd. */
+ @Param({"128bitAdd", "128bitAdd2", "64bitSum"})
+ private String name;
+
+ /** The action. */
+ private ToLongFunction<long[]> function;
+
+ /**
+ * @return the function
+ */
+ public ToLongFunction<long[]> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ if ("128bitAdd".equals(name)) {
+ function = x -> {
+ final Int128 s = Int128.create();
+ for (long y : x) {
+ s.add(y);
+ }
+ return s.hi64();
+ };
+ } else if ("128bitAdd2".equals(name)) {
+ function = x -> {
+ final Int128 s = Int128.create();
+ for (long y : x) {
+ s.add2(y);
+ }
+ return s.hi64();
+ };
+ } else if ("64bitSum".equals(name)) {
+ function = x -> {
+ long s = 0;
+ for (long y : x) {
+ s += y;
+ }
+ return s;
+ };
+ } else {
+ throw new IllegalStateException("Unknown long sum function: " + name);
+ }
+ }
+ }
+
+ /**
+ * Source of {@code long} array data to multiply as unsigned pairs.
+ * Magnitude is approximately controlled using a bit shift on the values.
+ */
+ @State(Scope.Benchmark)
+ public static class MultiplyLongDataSource {
+ /** Data length. */
+ @Param({"1024"})
+ private int length;
+ /** Data bit shift. */
+ @Param({"0", "33"})
+ private int shift;
+
+ /** Data. */
+ private long[] data;
+
+ /**
+ * @return the data
+ */
+ public long[] getData() {
+ return data;
+ }
+
+ /**
+ * Create the data.
+ * Data will be randomized per iteration.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ LongStream s = RandomSource.XO_RO_SHI_RO_128_PP.create().longs(length * 2L);
+ if (shift > 0) {
+ final int bits = shift;
+ s = s.map(x -> x >>> bits);
+ }
+ data = s.toArray();
+ }
+ }
+
+ /**
+ * Source of a {@link ToDoubleFunction} for a {@code long[]}.
+ */
+ @State(Scope.Benchmark)
+ public static class MultiplyLongFunctionSource {
+ /** Name of the source. */
+ @Param({"double", "unsignedMultiplyToDoubleBigInteger", "unsignedMultiplyToDouble"})
+ private String name;
+
+ /** The action. */
+ private ToDoubleFunction<long[]> function;
+
+ /**
+ * Function for two long arguments.
+ */
+ interface LongLongToDoubleFunction {
+ /**
+ * Apply the function.
+ *
+ * @param a Value.
+ * @param b Value.
+ * @return the result
+ */
+ double apply(long a, long b);
+ }
+
+ /**
+ * @return the function
+ */
+ public ToDoubleFunction<long[]> getFunction() {
+ return function;
+ }
+
+ /**
+ * Create the function.
+ */
+ @Setup(Level.Iteration)
+ public void setup() {
+ final LongLongToDoubleFunction f = createFunction(name);
+ function = x -> applyAll(x, f);
+ }
+
+ /**
+ * Creates the function.
+ *
+ * @param functionName Function name.
+ * @return the function
+ */
+ private LongLongToDoubleFunction createFunction(String functionName) {
+ if ("double".equals(functionName)) {
+ return (x, y) -> (double) x * y;
+ } else if ("unsignedMultiplyToDoubleBigInteger".equals(name)) {
+ return IntMath::unsignedMultiplyToDoubleBigInteger;
+ } else if ("unsignedMultiplyToDouble".equals(name)) {
+ return IntMath::unsignedMultiplyToDouble;
+ } else {
+ throw new IllegalStateException("Unknown multiply long function: " + name);
+ }
+ }
+
+ /**
+ * Apply the function to all pairs in the data.
+ *
+ * @param array Data.
+ * @param f Function.
+ * @return the result
+ */
+ private static double applyAll(long[] array, LongLongToDoubleFunction f) {
+ double s = 0;
+ for (int i = 0; i < array.length; i += 2) {
+ s += f.apply(array[i], array[i + 1]);
+ }
+ return s;
+ }
+ }
+
+ /**
+ * A mean of {@code int} data using a {@code long} sum.
+ */
+ static class LongSumMean implements IntConsumer, DoubleSupplier {
+ /** Count of values that have been added. */
+ private long n;
+
+ /** Sum of values that have been added. */
+ private long s;
+
+ @Override
+ public void accept(int value) {
+ s += value;
+ n++;
+ }
+
+ @Override
+ public double getAsDouble() {
+ return (double) s / n;
+ }
+
+ /**
+ * Compute the mean using a sum.
+ *
+ * @param data Data.
+ * @return the mean
+ */
+ static double mean(int[] data) {
+ long s = 0;
+ for (final int x : data) {
+ s += x;
+ }
+ return (double) s / data.length;
+ }
+ }
+
+ /**
+ * A mean of {@code long} data using a {@code BigInteger} sum.
+ */
+ static class BigIntegerSumMean implements LongConsumer, DoubleSupplier {
+ /** Count of values that have been added. */
+ private long n;
+
+ /** Sum of values that have been added. */
+ private BigInteger s = BigInteger.ZERO;
+
+ @Override
+ public void accept(long value) {
+ s = s.add(BigInteger.valueOf(value));
+ n++;
+ }
+
+ @Override
+ public double getAsDouble() {
+ return s.doubleValue() / n;
+ }
+
+ /**
+ * Compute the mean using a sum.
+ *
+ * @param data Data.
+ * @return the mean
+ */
+ static double mean(long[] data) {
+ BigInteger s = BigInteger.ZERO;
+ for (final long x : data) {
+ s = s.add(BigInteger.valueOf(x));
+ }
+ return s.doubleValue() / data.length;
+ }
+ }
+
+ /**
+ * Apply the action to each {@code int} value.
+ *
+ * @param <T> the action type
+ * @param action Action.
+ * @param values Values.
+ * @return the value
+ */
+ static <T extends IntConsumer & DoubleSupplier> double forEach(T action, int[] values) {
+ for (final int x : values) {
+ action.accept(x);
+ }
+ return action.getAsDouble();
+ }
+
+ /**
+ * Apply the action to each {@code double} value.
+ *
+ * @param <T> the action type
+ * @param action Action.
+ * @param values Values.
+ * @return the value
+ */
+ static <T extends DoubleConsumer & DoubleSupplier> double forEach(T action, double[] values) {
+ for (final double x : values) {
+ action.accept(x);
+ }
+ return action.getAsDouble();
+ }
+
+ /**
+ * Apply the action to each {@code long} value.
+ *
+ * @param <T> the action type
+ * @param action Action.
+ * @param values Values.
+ * @return the value
+ */
+ static <T extends LongConsumer & DoubleSupplier> double forEach(T action, long[] values) {
+ for (final long x : values) {
+ action.accept(x);
+ }
+ return action.getAsDouble();
+ }
+
+ /**
+ * Create the statistic using a consumer of {@code int} values.
+ *
+ * @param action Source of the data action.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double forEachIntStatistic(IntActionSource action, DataSource source) {
+ return forEach(action.getAction(), source.getData());
+ }
+
+ /**
+ * Create the statistic using a consumer of {@code double} values.
+ *
+ * @param action Source of the data action.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double forEachDoubleStatistic(DoubleActionSource action, DataSource source) {
+ return forEach(action.getAction(), source.getDoubleData());
+ }
+
+ /**
+ * Create the statistic using a consumer of {@code long} values.
+ *
+ * @param action Source of the data action.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double forEachLongStatistic(LongActionSource action, DataSource source) {
+ return forEach(action.getAction(), source.getLongData());
+ }
+
+ /**
+ * Create the statistic using a {@code int[]} function.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double arrayIntStatistic(IntFunctionSource function, DataSource source) {
+ return function.getFunction().applyAsDouble(source.getData());
+ }
+
+ /**
+ * Create the statistic using a {@code double[]} function.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double arrayDoubleStatistic(DoubleFunctionSource function, DataSource source) {
+ return function.getFunction().applyAsDouble(source.getDoubleData());
+ }
+
+ /**
+ * Create the statistic using a {@code long[]} function.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @return the statistic
+ */
+ @Benchmark
+ public double arrayLongStatistic(LongFunctionSource function, DataSource source) {
+ return function.getFunction().applyAsDouble(source.getLongData());
+ }
+
+ /**
+ * Create the variance using a aggregated {@code int[]} data.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @param bh Data sink.
+ */
+ @Benchmark
+ public void intVariance(IntVarianceFunctionSource function, IntVarianceDataSource source, Blackhole bh) {
+ final int size = source.size();
+ final ToDoubleFunction<IntVarianceData> f = function.getFunction();
+ for (int i = 0; i < size; i++) {
+ bh.consume(f.applyAsDouble(source.getData(i)));
+ }
+ }
+
+ /**
+ * Create the sum using a {@code long[]} function.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @return the sum
+ */
+ @Benchmark
+ public long longSum(LongSumFunctionSource function, LongDataSource source) {
+ return function.getFunction().applyAsLong(source.getData());
+ }
+
+ /**
+ * Create the product using a {@code long[]} function.
+ *
+ * @param function Source of the function.
+ * @param source Source of the data.
+ * @return the sum
+ */
+ @Benchmark
+ public double multiplyToDouble(MultiplyLongFunctionSource function, MultiplyLongDataSource source) {
+ return function.getFunction().applyAsDouble(source.getData());
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVariance2.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVariance2.java
new file mode 100644
index 0000000..451d569
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVariance2.java
@@ -0,0 +1,196 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.util.function.DoubleSupplier;
+import java.util.function.LongConsumer;
+
+/**
+ * Computes the variance of the available values.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.LongVariance} to allow benchmarking.
+ * This uses {@code java.lang.Math.multiplyHigh(long, long)} and requires Java 11+.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/variance">variance (Wikipedia)</a>
+ * @since 1.1
+ */
+final class LongVariance2 implements LongConsumer, DoubleSupplier {
+
+ /** Sum of the squared values. */
+ private final UInt192 sumSq;
+ /** Sum of the values. */
+ private final Int128 sum;
+ /** Count of values that have been added. */
+ private long n;
+
+ /** Flag to control if the statistic is biased, or should use a bias correction. */
+ private boolean biased;
+
+ /**
+ * Create an instance.
+ */
+ private LongVariance2() {
+ this(UInt192.create(), Int128.create(), 0);
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param sumSq Sum of the squared values.
+ * @param sum Sum of the values.
+ * @param n Count of values that have been added.
+ */
+ private LongVariance2(UInt192 sumSq, Int128 sum, int n) {
+ this.sumSq = sumSq;
+ this.sum = sum;
+ this.n = n;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * <p>The initial result is {@code NaN}.
+ *
+ * @return {@code IntVariance} instance.
+ */
+ public static LongVariance2 create() {
+ return new LongVariance2();
+ }
+
+ /**
+ * Returns an instance populated using the input {@code values}.
+ *
+ * @param values Values.
+ * @return {@code IntVariance} instance.
+ */
+ public static LongVariance2 of(long... values) {
+ // Note: Arrays could be processed using specialised counts knowing the maximum
+ // limit
+ // for an array is 2^31 values. Requires a UInt160.
+
+ final Int128 s = Int128.create();
+ final UInt192 ss = UInt192.create();
+ for (final long x : values) {
+ s.add(x);
+ ss.addSquare2(x);
+ }
+ return new LongVariance2(ss, s, values.length);
+ }
+
+ /**
+ * Updates the state of the statistic to reflect the addition of {@code value}.
+ *
+ * @param value Value.
+ */
+ @Override
+ public void accept(long value) {
+ sumSq.addSquare2(value);
+ sum.add(value);
+ n++;
+ }
+
+ /**
+ * Gets the variance of all input values.
+ *
+ * <p>When no values have been added, the result is {@code NaN}.
+ *
+ * @return variance of all values.
+ */
+ @Override
+ public double getAsDouble() {
+ if (n == 0) {
+ return Double.NaN;
+ }
+ // Avoid a divide by zero
+ if (n == 1) {
+ return 0;
+ }
+ final long n0 = biased ? n : n - 1;
+
+ // Sum-of-squared deviations: sum(x^2) - sum(x)^2 / n
+ // Sum-of-squared deviations precursor: n * sum(x^2) - sum(x)^2
+ // The precursor is computed in integer precision.
+ // The divide uses double precision.
+ // This ensures we avoid cancellation in the difference and use a fast divide.
+ // The result is limited to max 4 ulp by the rounding in the double computation
+ // When n0*n is < 2^53 the max error is reduced to two roundings.
+
+ // Compute the term if possible using fast integer arithmetic.
+ // 192-bit sum(x^2) * n will be OK when the upper 32-bits are zero.
+ // 128-bit sum(x)^2 will be OK when the upper 64-bits are zero.
+ // The first is safe when n < 2^32 but we must check the sum high bits.
+ double diff;
+ if (((n >>> Integer.SIZE) | sum.hi64()) == 0) {
+ diff = sumSq.unsignedMultiply((int) n).subtract(sum.squareLow()).toDouble();
+ } else {
+ diff = sumSq.toBigInteger().multiply(BigInteger.valueOf(n))
+ .subtract(square(sum.toBigInteger())).doubleValue();
+ }
+ // Compute the divide in double precision
+ return diff / IntMath.unsignedMultiplyToDouble(n, n0);
+ }
+
+ /**
+ * Convenience method to square a BigInteger.
+ *
+ * @param x Value
+ * @return x^2
+ */
+ private static BigInteger square(BigInteger x) {
+ return x.multiply(x);
+ }
+
+ /**
+ * Combine with the {@code other} instance.
+ *
+ * @param other Other instance.
+ * @return this instance
+ */
+ public LongVariance2 combine(LongVariance2 other) {
+ sumSq.add(other.sumSq);
+ sum.add(other.sum);
+ n += other.n;
+ return this;
+ }
+
+ /**
+ * Sets the value of the biased flag. The default value is {@code false}.
+ *
+ * <p>If {@code false} the sum of squared deviations from the sample mean is
+ * normalised by {@code n - 1} where {@code n} is the number of samples. This is
+ * Bessel's correction for an unbiased estimator of the variance of a hypothetical
+ * infinite population.
+ *
+ * <p>If {@code true} the sum of squared deviations is normalised by the number of
+ * samples {@code n}.
+ *
+ * <p>Note: This option only applies when {@code n > 1}. The variance of {@code n = 1}
+ * is always 0.
+ *
+ * <p>This flag only controls the final computation of the statistic. The value of
+ * this flag will not affect compatibility between instances during a
+ * {@link #combine(LongVariance2) combine} operation.
+ *
+ * @param v Value.
+ * @return {@code this} instance
+ */
+ public LongVariance2 setBiased(boolean v) {
+ biased = v;
+ return this;
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128.java
new file mode 100644
index 0000000..5f6b5f3
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128.java
@@ -0,0 +1,285 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import org.apache.commons.numbers.core.DD;
+
+/**
+ * A 128-bit unsigned integer.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.UInt128} to allow benchmarking.
+ * Additional methods may have been added for comparative benchmarks.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values
+ * generated by squaring {@code int} values.
+ *
+ * @since 1.1
+ */
+final class UInt128 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Data is stored using integers to allow efficient sum-with-carry addition
+
+ /** low 32-bits. */
+ private int d;
+ /** low 32-bits. */
+ private int c;
+ /** high 64-bits. */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt128() {
+ // No-op
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param x Value.
+ */
+ private UInt128(long x) {
+ d = (int) x;
+ c = (int) (x >>> Integer.SIZE);
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ *
+ * @param hi High 64-bits.
+ * @param mid Middle 32-bits.
+ * @param lo Low 32-bits.
+ */
+ private UInt128(long hi, int mid, int lo) {
+ this.d = lo;
+ this.c = mid;
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 64-bits.
+ */
+ UInt128(long hi, long lo) {
+ this.d = (int) lo;
+ this.c = (int) (lo >>> Integer.SIZE);
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt128 create() {
+ return new UInt128();
+ }
+
+ /**
+ * Create an instance of the {@code long} value.
+ * The value is assumed to be an unsigned 64-bit integer.
+ *
+ * @param x Value (must be positive).
+ * @return the instance
+ */
+ static UInt128 of(long x) {
+ return new UInt128(x);
+ }
+
+ /**
+ * Create an instance of the {@code UInt96} value.
+ *
+ * @param x Value.
+ * @return the instance
+ */
+ static UInt128 of(UInt96 x) {
+ final int lo = x.lo32();
+ final long hi = x.hi64();
+ final UInt128 y = new UInt128();
+ y.d = lo;
+ y.c = (int) hi;
+ y.ab = hi >>> Integer.SIZE;
+ return y;
+ }
+
+ /**
+ * Adds the value in place. It is assumed to be positive, for example the square of an
+ * {@code int} value. However no check is performed for a negative value.
+ *
+ * <p>Note: This addition handles {@value Long#MIN_VALUE} as an unsigned
+ * value of 2^63.
+ *
+ * @param x Value.
+ */
+ void addPositive(long x) {
+ // Sum with carry.
+ // Assuming x is positive then x + lo will not overflow 64-bits
+ // so we do not have to split x into upper and lower 32-bit values.
+ long s = x + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value in-place.
+ *
+ * @param x Value.
+ */
+ void add(UInt128 x) {
+ // Avoid issues adding to itself
+ final int dd = x.d;
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ long s = (dd & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+ /**
+ * Multiply by the unsigned value.
+ * Any overflow bits are lost.
+ *
+ * @param x Value.
+ * @return the product
+ */
+ UInt128 unsignedMultiply(int x) {
+ final long xx = x & MASK32;
+ // Multiply with carry.
+ long product = xx * (d & MASK32);
+ final int dd = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (c & MASK32);
+ final int cc = (int) product;
+ // Possible overflow here and bits are lost
+ final long aabb = (product >>> Integer.SIZE) + xx * ab;
+ return new UInt128(aabb, cc, dd);
+ }
+
+ /**
+ * Subtracts the value.
+ * Any overflow bits (negative result) are lost.
+ *
+ * @param x Value.
+ * @return the difference
+ */
+ UInt128 subtract(UInt128 x) {
+ // Difference with carry.
+ long diff = (d & MASK32) - (x.d & MASK32);
+ final int dd = (int) diff;
+ diff = (diff >> Integer.SIZE) + (c & MASK32) - (x.c & MASK32);
+ final int cc = (int) diff;
+ // Possible overflow here and bits are lost containing info on the
+ // magnitude of the true negative value
+ final long aabb = (diff >> Integer.SIZE) + ab - x.ab;
+ return new UInt128(aabb, cc, dd);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ // Test if we have more than 63-bits
+ if (ab != 0 || c < 0) {
+ ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES * 4)
+ .putLong(ab)
+ .putInt(c)
+ .putInt(d);
+ return new BigInteger(1, bb.array());
+ }
+ // Create from a long
+ return BigInteger.valueOf(((c & MASK32) << Integer.SIZE) | (d & MASK32));
+ }
+
+ /**
+ * Convert to a double-double.
+ *
+ * @return the value
+ */
+ DD toDD() {
+ // Sum low to high
+ return DD.ofSum(d & MASK32, (c & MASK32) * 0x1.0p32)
+ .add((ab & MASK32) * 0x1.0p64)
+ .add((ab >>> Integer.SIZE) * 0x1.0p96);
+ }
+
+ /**
+ * Convert to a {@code double}.
+ *
+ * @return the value
+ */
+ double toDouble() {
+ return IntMath.uin128ToDouble(hi64(), lo64());
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * @return the low 64-bits
+ */
+ long lo64() {
+ return (d & MASK32) | ((c & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the low 32-bits as an {@code int} value.
+ *
+ * @return bits 32-1
+ */
+ int lo32() {
+ return d;
+ }
+
+ /**
+ * Return the middle 32-bits as an {@code int} value.
+ *
+ * @return bits 64-33
+ */
+ int mid32() {
+ return c;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 128-65
+ */
+ long hi64() {
+ return ab;
+ }
+
+ /**
+ * Return the higher 32-bits as an {@code int} value.
+ *
+ * @return bits 128-97
+ */
+ int hi32() {
+ return (int) (ab >>> Integer.SIZE);
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192.java
new file mode 100644
index 0000000..7dd3de2
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192.java
@@ -0,0 +1,297 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import org.apache.commons.numbers.core.DD;
+
+/**
+ * A 192-bit unsigned integer.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.UInt192} to allow benchmarking.
+ * Additional methods may have been added for comparative benchmarks.
+ *
+ * <p>This is a specialised class to implement an accumulator of squared {@code long} values.
+ *
+ * @since 1.1
+ */
+final class UInt192 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Data is stored using integers to allow efficient sum-with-carry addition
+
+ /** bits 32-1 (low 32-bits). */
+ private int f;
+ /** bits 64-33. */
+ private int e;
+ /** bits 96-65. */
+ private int d;
+ /** bits 128-97. */
+ private int c;
+ /** bits 192-129 (high 64-bits). */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt192() {
+ // No-op
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param mid Middle 64-bits.
+ * @param lo Low 64-bits.
+ */
+ UInt192(long hi, long mid, long lo) {
+ this.f = (int) lo;
+ this.e = (int) (lo >>> Integer.SIZE);
+ this.d = (int) mid;
+ this.c = (int) (mid >>> Integer.SIZE);
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ *
+ * @param ab bits 192-129 (high 64-bits).
+ * @param c bits 128-97.
+ * @param d bits 96-65.
+ * @param e bits 64-33.
+ * @param f bits 32-1.
+ */
+ private UInt192(long ab, int c, int d, int e, int f) {
+ this.ab = ab;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt192 create() {
+ return new UInt192();
+ }
+
+ /**
+ * Adds the squared value {@code x * x}.
+ *
+ * @param x Value.
+ */
+ void addSquare(long x) {
+ final long lo = x * x;
+ final long hi = IntMath.squareHigh(x);
+
+ // Sum with carry.
+ long s = (lo & MASK32) + (f & MASK32);
+ f = (int) s;
+ s = (s >>> Integer.SIZE) + (lo >>> Integer.SIZE) + (e & MASK32);
+ e = (int) s;
+ s = (s >>> Integer.SIZE) + (hi & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (hi >>> Integer.SIZE) + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the squared value {@code x * x}.
+ *
+ * <p>This uses {@code java.lang.Math.multiplyHigh(long, long)} and requires Java 11+.
+ *
+ * @param x Value.
+ */
+ void addSquare2(long x) {
+ final long lo = x * x;
+ final long hi = Math.multiplyHigh(x, x);
+
+ // Sum with carry.
+ long s = (lo & MASK32) + (f & MASK32);
+ f = (int) s;
+ s = (s >>> Integer.SIZE) + (lo >>> Integer.SIZE) + (e & MASK32);
+ e = (int) s;
+ s = (s >>> Integer.SIZE) + (hi & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (hi >>> Integer.SIZE) + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(UInt192 x) {
+ // Avoid issues adding to itself
+ final int ff = x.f;
+ final int ee = x.e;
+ final int dd = x.d;
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ long s = (ff & MASK32) + (f & MASK32);
+ f = (int) s;
+ s = (s >>> Integer.SIZE) + (ee & MASK32) + (e & MASK32);
+ e = (int) s;
+ s = (s >>> Integer.SIZE) + (dd & MASK32) + (d & MASK32);
+ d = (int) s;
+ s = (s >>> Integer.SIZE) + (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+
+ /**
+ * Multiply by the unsigned value.
+ * Any overflow bits are lost.
+ *
+ * @param x Value.
+ * @return the product
+ */
+ UInt192 unsignedMultiply(int x) {
+ final long xx = x & MASK32;
+ // Multiply with carry.
+ long product = xx * (f & MASK32);
+ final int ff = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (e & MASK32);
+ final int ee = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (d & MASK32);
+ final int dd = (int) product;
+ product = (product >>> Integer.SIZE) + xx * (c & MASK32);
+ final int cc = (int) product;
+ // Possible overflow here and bits are lost
+ final long aabb = (product >>> Integer.SIZE) + xx * ab;
+ return new UInt192(aabb, cc, dd, ee, ff);
+ }
+
+ /**
+ * Subtracts the value.
+ * Any overflow bits (negative result) are lost.
+ *
+ * @param x Value.
+ * @return the difference
+ */
+ UInt192 subtract(UInt128 x) {
+ // Difference with carry.
+ // Subtract common part.
+ long diff = (f & MASK32) - (x.lo32() & MASK32);
+ final int ff = (int) diff;
+ diff = (diff >> Integer.SIZE) + (e & MASK32) - (x.mid32() & MASK32);
+ final int ee = (int) diff;
+ diff = (diff >> Integer.SIZE) + (d & MASK32) - (x.hi64() & MASK32);
+ final int dd = (int) diff;
+ diff = (diff >> Integer.SIZE) + (c & MASK32) - (x.hi64() >>> Integer.SIZE);
+ final int cc = (int) diff;
+ // Possible overflow here and bits are lost containing info on the
+ // magnitude of the true negative value
+ final long aabb = (diff >> Integer.SIZE) + ab;
+ return new UInt192(aabb, cc, dd, ee, ff);
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ final ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES * 6)
+ .putLong(ab)
+ .putInt(c)
+ .putInt(d)
+ .putInt(e)
+ .putInt(f);
+ // Sign is always positive. This works for zero.
+ return new BigInteger(1, bb.array());
+ }
+
+ /**
+ * Convert to a double.
+ *
+ * @return the value
+ */
+ double toDouble() {
+ long h = hi64();
+ long m = mid64();
+ long l = lo64();
+ if (h == 0) {
+ return IntMath.uin128ToDouble(m, l);
+ }
+ // For correct rounding we use a sticky bit to represent magnitude
+ // lost from the low 64-bits. The result is scaled by 2^64.
+ return IntMath.uin128ToDouble(h, m | ((l != 0) ? 1 : 0)) * 0x1.0p64;
+ }
+
+ /**
+ * Convert to a double-double.
+ *
+ * @return the value
+ */
+ DD toDD() {
+ // Sum low to high
+ return DD.ofSum(f & MASK32, (e & MASK32) * 0x1.0p32)
+ .add((d & MASK32) * 0x1.0p64)
+ .add((c & MASK32) * 0x1.0p96)
+ .add((ab & MASK32) * 0x1.0p128)
+ .add((ab >>> Integer.SIZE) * 0x1.0p160);
+ }
+
+ /**
+ * Return the lower 64-bits as a {@code long} value.
+ *
+ * @return the low 64-bits
+ */
+ long lo64() {
+ return (f & MASK32) | ((e & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the middle 64-bits as a {@code long} value.
+ *
+ * @return bits 128-65
+ */
+ long mid64() {
+ return (d & MASK32) | ((c & MASK32) << Integer.SIZE);
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 192-129
+ */
+ long hi64() {
+ return ab;
+ }
+
+ /**
+ * Return the higher 32-bits as an {@code int} value.
+ *
+ * @return the high 32-bits
+ */
+ int hi32() {
+ return (int) (ab >>> Integer.SIZE);
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96.java b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96.java
new file mode 100644
index 0000000..d7e1659
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/main/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96.java
@@ -0,0 +1,170 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import org.apache.commons.numbers.core.DD;
+
+/**
+ * A 96-bit unsigned integer.
+ *
+ * <p>This is a copy of {@code o.a.c.statistics.descriptive.Int96} to allow benchmarking.
+ * Additional methods may have been added for comparative benchmarks.
+ *
+ * <p>This is a specialised class to implement an accumulator of {@code long} values
+ * generated by squaring {@code int} values from an array (max observations=2^31).
+ *
+ * @since 1.1
+ */
+final class UInt96 {
+ /** Mask for the lower 32-bits of a long. */
+ private static final long MASK32 = 0xffff_ffffL;
+
+ // Low data is stored using an integer to allow efficient sum-with-carry addition
+
+ /** bits 32-1 (low 32-bits). */
+ private int c;
+ /** bits 96-33. */
+ private long ab;
+
+ /**
+ * Create an instance.
+ */
+ private UInt96() {
+ // No-op
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param x Value.
+ */
+ private UInt96(long x) {
+ c = (int) x;
+ ab = (int) (x >>> Integer.SIZE);
+ }
+
+ /**
+ * Create an instance using a direct binary representation.
+ * This is package-private for testing.
+ *
+ * @param hi High 64-bits.
+ * @param lo Low 32-bits.
+ */
+ UInt96(long hi, int lo) {
+ this.c = lo;
+ this.ab = hi;
+ }
+
+ /**
+ * Create an instance. The initial value is zero.
+ *
+ * @return the instance
+ */
+ static UInt96 create() {
+ return new UInt96();
+ }
+
+ /**
+ * Create an instance of the {@code long} value.
+ * The value is assumed to be an unsigned 64-bit integer.
+ *
+ * @param x Value (must be positive).
+ * @return the instance
+ */
+ static UInt96 of(long x) {
+ return new UInt96(x);
+ }
+
+ /**
+ * Adds the value. It is assumed to be positive, for example the square of an
+ * {@code int} value. However no check is performed for a negative value.
+ *
+ * <p>Note: This addition handles {@value Long#MIN_VALUE} as an unsigned
+ * value of 2^63.
+ *
+ * @param x Value.
+ */
+ void addPositive(long x) {
+ // Sum with carry.
+ // Assuming x is positive then x + lo will not overflow 64-bits
+ // so we do not have to split x into upper and lower 32-bit values.
+ final long s = x + (c & MASK32);
+ c = (int) s;
+ ab += s >>> Integer.SIZE;
+ }
+
+ /**
+ * Adds the value.
+ *
+ * @param x Value.
+ */
+ void add(UInt96 x) {
+ // Avoid issues adding to itself
+ final int cc = x.c;
+ final long aabb = x.ab;
+ // Sum with carry.
+ final long s = (cc & MASK32) + (c & MASK32);
+ c = (int) s;
+ ab += (s >>> Integer.SIZE) + aabb;
+ }
+
+ /**
+ * Convert to a BigInteger.
+ *
+ * @return the value
+ */
+ BigInteger toBigInteger() {
+ if (ab != 0) {
+ final ByteBuffer bb = ByteBuffer.allocate(Integer.BYTES * 3)
+ .putLong(ab)
+ .putInt(c);
+ return new BigInteger(1, bb.array());
+ }
+ return BigInteger.valueOf(c & MASK32);
+ }
+
+ /**
+ * Convert to a double-double.
+ *
+ * @return the value
+ */
+ DD toDD() {
+ // Sum low to high
+ return DD.ofSum(c & MASK32, (ab & MASK32) * 0x1.0p32)
+ .add((ab >>> Integer.SIZE) * 0x1.0p64);
+ }
+
+ /**
+ * Return the lower 32-bits as an {@code int} value.
+ *
+ * @return bits 32-1
+ */
+ int lo32() {
+ return c;
+ }
+
+ /**
+ * Return the higher 64-bits as a {@code long} value.
+ *
+ * @return bits 96-33
+ */
+ long hi64() {
+ return ab;
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128Test.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128Test.java
new file mode 100644
index 0000000..2e490f0
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/Int128Test.java
@@ -0,0 +1,194 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link Int128}.
+ */
+class Int128Test {
+ private static final BigInteger TWO_POW_128 = BigInteger.ONE.shiftLeft(128);
+ private static final BigInteger TWO_POW_127 = BigInteger.ONE.shiftLeft(127);
+ private static final BigInteger MINUS_TWO_POW_127 = BigInteger.ONE.shiftLeft(127).negate();
+
+ @Test
+ void testCreate() {
+ final Int128 v = Int128.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testAddLong"})
+ void testToBigInteger(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).shiftLeft(64).add(BigInteger.valueOf(b));
+ final Int128 v = new Int128(a, b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final Int128 v = Int128.of(a);
+ v.add(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testAddLong"})
+ void testAddLong2(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final Int128 v = Int128.of(a);
+ v.add2(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, 2, Long.MIN_VALUE, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ builder.accept(Arguments.of(i, -j));
+ builder.accept(Arguments.of(-i, j));
+ builder.accept(Arguments.of(-i, -j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final Int128 v = Int128.create();
+ for (final long x : a) {
+ v.add(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), v.toDD(), 0x1.0p-106, "DD");
+ }
+
+ @ParameterizedTest
+ @MethodSource(value = {"testAddLongs"})
+ void testAddLongs2(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final Int128 v = Int128.create();
+ for (final long x : a) {
+ v.add2(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> -(x >>> 2)).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, long b, long c, long d) {
+ final Int128 x = new Int128(a, b);
+ final Int128 y = new Int128(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo64());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The Int128 result is a signed 128-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [2^-127, 2^127).
+ // Since the overflow will be at most 1-bit we can wrap the value
+ // using +/- 2^128.
+ if (expected.compareTo(TWO_POW_127) >= 0) {
+ // too high
+ expected = expected.subtract(TWO_POW_128);
+ } else if (expected.compareTo(MINUS_TWO_POW_127) < 0) {
+ // too low
+ expected = expected.add(TWO_POW_128);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), x.toDD(), 0x1.0p-106, "DD");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.compareTo(TWO_POW_127) >= 0) {
+ // too high
+ expected = expected.subtract(TWO_POW_128);
+ } else if (expected.compareTo(MINUS_TWO_POW_127) < 0) {
+ // too low
+ expected = expected.add(TWO_POW_128);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 1, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSquareLow(long a) {
+ final BigInteger expected = BigInteger.valueOf(a).pow(2);
+ final UInt128 v = Int128.of(a).squareLow();
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static LongStream testSquareLow() {
+ final LongStream.Builder builder = LongStream.builder();
+ final long[] x = {0, 1, Long.MIN_VALUE, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ builder.accept(i);
+ builder.accept(-i);
+ }
+ RandomSource.XO_RO_SHI_RO_128_PP.create().longs(20).forEach(builder);
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMathTest.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMathTest.java
new file mode 100644
index 0000000..d1bc9cf
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/IntMathTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigInteger;
+import java.util.stream.LongStream;
+import java.util.stream.LongStream.Builder;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link IntMath}.
+ */
+class IntMathTest {
+ /** 2^63. */
+ private static final BigInteger TWO_POW_63 = BigInteger.ONE.shiftLeft(63);
+
+ @ParameterizedTest
+ @MethodSource
+ void testSquareHigh(long a) {
+ final long actual = IntMath.squareHigh(a);
+ final long expected = BigInteger.valueOf(a).pow(2).shiftRight(64).longValue();
+ Assertions.assertEquals(expected, actual);
+ }
+
+ static LongStream testSquareHigh() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Builder builder = LongStream.builder();
+ builder.accept(0);
+ builder.accept(Long.MAX_VALUE);
+ builder.accept(Long.MIN_VALUE);
+ rng.ints(5).forEach(builder::accept);
+ rng.longs(50).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 1).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 2).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 5).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 13).forEach(builder::accept);
+ rng.longs(10).map(x -> x >>> 35).forEach(builder::accept);
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testUnsignedMultiplyHigh(long a, long b) {
+ final long actual = IntMath.unsignedMultiplyHigh(a, b);
+ final BigInteger bi1 = toUnsignedBigInteger(a);
+ final BigInteger bi2 = toUnsignedBigInteger(b);
+ final BigInteger expected = bi1.multiply(bi2);
+ Assertions.assertEquals(expected.shiftRight(Long.SIZE).longValue(), actual,
+ () -> String.format("%s * %s", bi1, bi2));
+ final double x = expected.doubleValue();
+ Assertions.assertEquals(x, IntMath.unsignedMultiplyToDoubleBigInteger(a, b),
+ () -> String.format("double %s * %s", bi1, bi2));
+ Assertions.assertEquals(x, IntMath.unsignedMultiplyToDouble(a, b),
+ () -> String.format("double2 %s * %s", bi1, bi2));
+ }
+
+ static Stream<Arguments> testUnsignedMultiplyHigh() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] values = {
+ -1, 0, 1, Long.MAX_VALUE, Long.MIN_VALUE,
+ 0xffL, 0xff00L, 0xff0000L, 0xff000000L,
+ 0xff00000000L, 0xff0000000000L, 0xff000000000000L, 0xff000000000000L,
+ 0xffffL, 0xffff0000L, 0xffff00000000L, 0xffff000000000000L,
+ 0xffffffffL, 0xffffffff00000000L
+ };
+ for (final long v1 : values) {
+ for (final long v2 : values) {
+ builder.accept(Arguments.of(v1, v2));
+ builder.accept(Arguments.of(v1 >>> 15, v2 >>> 18));
+ }
+ }
+ for (int i = 0; i < 200; i++) {
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Create a big integer treating the value as unsigned.
+ *
+ * @param v Value
+ * @return the big integer
+ */
+ private static BigInteger toUnsignedBigInteger(long v) {
+ return v < 0 ?
+ TWO_POW_63.add(BigInteger.valueOf(v & Long.MAX_VALUE)) :
+ BigInteger.valueOf(v);
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testUin128ToDouble(long a, long b) {
+ final BigInteger bi1 = toUnsignedBigInteger(a).shiftLeft(Long.SIZE);
+ final BigInteger bi2 = toUnsignedBigInteger(b);
+ final double x = bi1.add(bi2).doubleValue();
+ Assertions.assertEquals(x, IntMath.uin128ToDouble(a, b),
+ () -> String.format("%s + %s", a, b));
+ }
+
+ static Stream<Arguments> testUin128ToDouble() {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ for (int i = 0; i < 100; i++) {
+ long a = rng.nextLong();
+ long b = rng.nextLong();
+ builder.accept(Arguments.of(a, b));
+ builder.accept(Arguments.of(0, b));
+ // Edge cases where trailing bits are required for rounding.
+ // Create a 55-bit number. Ensure the highest bit is set.
+ a = (a << 9) | Long.MIN_VALUE;
+ // Shift right and carry bits down.
+ int shift = rng.nextInt(1, 64);
+ long c = a >>> shift;
+ long d = a << -shift;
+ // Check
+ Assertions.assertEquals(Long.bitCount(a), Long.bitCount(c) + Long.bitCount(d));
+ builder.accept(Arguments.of(c, d));
+ // Add a trailing bit that may change rounding
+ builder.accept(Arguments.of(c, d | 1));
+ }
+ // At least one case where the trailing bit does effect rounding
+ // 54-bits all set is an odd number + 0.5
+ builder.accept(Arguments.of(1, (1L << 11)));
+ builder.accept(Arguments.of(1, (1L << 11) | 1));
+ // Unset the second to last bit and repeat above is an even number + 0.5
+ builder.accept(Arguments.of(1, ((1L & ~0x2) << 11)));
+ builder.accept(Arguments.of(1, ((1L & ~0x2) << 11) | 1));
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVarianceTest.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVarianceTest.java
new file mode 100644
index 0000000..6d4fa21
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/LongVarianceTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.apache.commons.statistics.descriptive.LongVariance;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Test for {@link LongVariance2}. Tested against {@link LongVariance}.
+ */
+class LongVarianceTest {
+ @ParameterizedTest
+ @ValueSource(ints = {0, 1, 2, 5, 10, 13, 1000})
+ void testVariance(int n) {
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final long[] values = rng.longs(n).toArray();
+ final LongVariance v1 = LongVariance.of(values);
+ final LongVariance2 v2 = LongVariance2.of(values);
+ double variance = v1.getAsDouble();
+ final double actual = v1.getAsDouble();
+ Assertions.assertEquals(variance, actual, "Variance");
+
+ if (n > 1) {
+ variance = v1.setBiased(true).getAsDouble();
+ Assertions.assertEquals(variance, v2.setBiased(true).getAsDouble(), "Variance biased");
+ }
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/TestUtils.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/TestUtils.java
new file mode 100644
index 0000000..7144a6a
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/TestUtils.java
@@ -0,0 +1,159 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.function.Supplier;
+import org.apache.commons.numbers.core.DD;
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * Test utilities.
+ */
+final class TestUtils {
+ /** No instances. */
+ private TestUtils() {}
+
+ // DD equality checks adapted from o.a.c.numbers.core.TestUtils
+
+ /**
+ * Assert the two numbers are equal within the provided relative error.
+ *
+ * <p>The provided error is relative to the exact result in expected: (e - a) / e.
+ * If expected is zero this division is undefined. In this case the actual must be zero
+ * (no absolute tolerance is supported). The reporting of the error uses the absolute
+ * error and the return value of the relative error is 0. Cases of complete cancellation
+ * should be avoided for benchmarking relative accuracy.
+ *
+ * <p>Note that the actual double-double result is not validated using the high and low
+ * parts individually. These are summed and compared to the expected.
+ *
+ * <p>Set {@code eps} to negative to report the relative error to the stdout and
+ * ignore failures.
+ *
+ * <p>The relative error is signed. The sign of the error
+ * is the same as that returned from Double.compare(actual, expected); it is
+ * computed using {@code actual - expected}.
+ *
+ * @param expected expected value
+ * @param actual actual value
+ * @param eps maximum relative error between the two values
+ * @param msg failure message
+ * @return relative error difference between the values (signed)
+ * @throws NumberFormatException if {@code actual} contains non-finite values
+ */
+ static double assertEquals(BigDecimal expected, DD actual, double eps, String msg) {
+ return assertEquals(expected, actual, eps, () -> msg);
+ }
+
+ /**
+ * Assert the two numbers are equal within the provided relative error.
+ *
+ * <p>The provided error is relative to the exact result in expected: (e - a) / e.
+ * If expected is zero this division is undefined. In this case the actual must be zero
+ * (no absolute tolerance is supported). The reporting of the error uses the absolute
+ * error and the return value of the relative error is 0. Cases of complete cancellation
+ * should be avoided for benchmarking relative accuracy.
+ *
+ * <p>Note that the actual double-double result is not validated using the high and low
+ * parts individually. These are summed and compared to the expected.
+ *
+ * <p>Set {@code eps} to negative to report the relative error to the stdout and
+ * ignore failures.
+ *
+ * <p>The relative error is signed. The sign of the error
+ * is the same as that returned from Double.compare(actual, expected); it is
+ * computed using {@code actual - expected}.
+ *
+ * @param expected expected value
+ * @param actual actual value
+ * @param eps maximum relative error between the two values
+ * @param msg failure message
+ * @return relative error difference between the values (signed)
+ * @throws NumberFormatException if {@code actual} contains non-finite values
+ */
+ static double assertEquals(BigDecimal expected, DD actual, double eps, Supplier<String> msg) {
+ // actual - expected
+ final BigDecimal delta = new BigDecimal(actual.hi())
+ .add(new BigDecimal(actual.lo()))
+ .subtract(expected);
+ boolean equal;
+ if (expected.compareTo(BigDecimal.ZERO) == 0) {
+ // Edge case. Currently an absolute tolerance is not supported as summation
+ // to zero cases generated in testing all pass.
+ equal = actual.doubleValue() == 0;
+
+ // DEBUG:
+ if (eps < 0) {
+ if (!equal) {
+ printf("%sexpected 0 != actual <%s + %s> (abs.error=%s)%n",
+ prefix(msg), actual.hi(), actual.lo(), delta.doubleValue());
+ }
+ } else if (!equal) {
+ Assertions.fail(String.format("%sexpected 0 != actual <%s + %s> (abs.error=%s)",
+ prefix(msg), actual.hi(), actual.lo(), delta.doubleValue()));
+ }
+
+ return 0;
+ }
+
+ final double rel = delta.divide(expected, MathContext.DECIMAL128).doubleValue();
+ // Allow input of a negative maximum ULPs
+ equal = Math.abs(rel) <= Math.abs(eps);
+
+ // DEBUG:
+ if (eps < 0) {
+ if (!equal) {
+ printf("%sexpected <%s> != actual <%s + %s> (rel.error=%s (%.3f x tol))%n",
+ prefix(msg), expected.round(MathContext.DECIMAL128), actual.hi(), actual.lo(),
+ rel, Math.abs(rel) / eps);
+ }
+ } else if (!equal) {
+ Assertions.fail(String.format("%sexpected <%s> != actual <%s + %s> (rel.error=%s (%.3f x tol))",
+ prefix(msg), expected.round(MathContext.DECIMAL128), actual.hi(), actual.lo(),
+ rel, Math.abs(rel) / eps));
+ }
+
+ return rel;
+ }
+
+ /**
+ * Print a formatted message to stdout.
+ * Provides a single point to disable checkstyle warnings on print statements and
+ * enable/disable all print debugging.
+ *
+ * @param format Format string.
+ * @param args Arguments.
+ */
+ static void printf(String format, Object... args) {
+ // CHECKSTYLE: stop regex
+ System.out.printf(format, args);
+ // CHECKSTYLE: resume regex
+ }
+
+ /**
+ * Get the prefix for the message.
+ *
+ * @param msg Message supplier
+ * @return the prefix
+ */
+ static String prefix(Supplier<String> msg) {
+ return msg == null ? "" : msg.get() + ": ";
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128Test.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128Test.java
new file mode 100644
index 0000000..db002c5
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt128Test.java
@@ -0,0 +1,238 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt128}.
+ */
+class UInt128Test {
+ @Test
+ void testCreate() {
+ final UInt128 v = UInt128.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @Test
+ void testAddLongMinValue() {
+ final UInt128 v = UInt128.of(1268361283468345237L);
+ final BigInteger x = BigInteger.ONE.shiftLeft(63);
+ BigInteger expected = v.toBigInteger();
+ for (int i = 1; i <= 5; i++) {
+ // Accepts a negative value without exception. This is
+ // computed correctly if the current low 32 bits
+ // added to the argument do not overflow. This is always
+ // true for min value as all lower 32-bits are zero.
+ v.addPositive(Long.MIN_VALUE);
+ expected = expected.add(x);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final UInt128 v = UInt128.of(a);
+ v.addPositive(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt128 v = UInt128.create();
+ for (final long x : a) {
+ Assertions.assertFalse(x < 0, "Value must be positive");
+ v.addPositive(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), v.toDD(), 0x1.0p-106, "DD");
+ Assertions.assertEquals(expected.doubleValue(), v.toDouble(), "double");
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, long b, long c, long d) {
+ final UInt128 x = new UInt128(a, b);
+ final UInt128 y = new UInt128(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo64());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 128-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^128).
+ if (expected.testBit(128)) {
+ expected = expected.flipBit(128);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), x.toDD(), 0x1.0p-106, "DD");
+ Assertions.assertEquals(expected.doubleValue(), x.toDouble(), "double");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(128)) {
+ expected = expected.flipBit(128);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong() >>> 1, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong() >>> 2, rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testOfInt96(long a, int b) {
+ final UInt96 x = new UInt96(a, b);
+ final UInt128 y = UInt128.of(x);
+ Assertions.assertEquals(x.toBigInteger(), y.toBigInteger());
+ }
+
+ static Stream<Arguments> testOfInt96() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final int b = rng.nextInt();
+ builder.accept(Arguments.of(a, b));
+ builder.accept(Arguments.of(0, b));
+ builder.accept(Arguments.of(a, 0));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testMultiplyInt(long a, long b, int n) {
+ assertMultiplyInt(a, b, n);
+ assertMultiplyInt(a >>> 32, b, n);
+ assertMultiplyInt(0, b, n);
+ }
+
+ private static void assertMultiplyInt(long a, long b, int n) {
+ final UInt128 v = new UInt128(a, b);
+ BigInteger expected = v.toBigInteger().multiply(BigInteger.valueOf(n & 0xffff_ffffL));
+ // Clip to 128-bits. Only required if the upper 32-bits are non-zero.
+ final int len = expected.bitLength();
+ if (len > 128 && v.hi32() != 0) {
+ expected = expected.subtract(expected.shiftRight(128).shiftLeft(128));
+ }
+ Assertions.assertEquals(expected, v.unsignedMultiply(n).toBigInteger());
+ }
+
+ static Stream<Arguments> testMultiplyInt() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final int[] x = {0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ for (final int n : x) {
+ builder.accept(Arguments.of(a, b, n));
+ }
+ for (int j = 0; j < 5; j++) {
+ builder.accept(Arguments.of(a, b, rng.nextInt()));
+ }
+ }
+ builder.accept(Arguments.of(-1L >>> 32, -1L, -1));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSubtract(long a, long b, long c, long d) {
+ assertSubtract(a, b, c, d);
+ assertSubtract(c, d, a, b);
+ }
+
+ private static void assertSubtract(long a, long b, long c, long d) {
+ final UInt128 x = new UInt128(a, b);
+ final UInt128 y = new UInt128(c, d);
+ BigInteger expected = x.toBigInteger().subtract(y.toBigInteger());
+ if (expected.signum() < 0) {
+ expected = expected.add(BigInteger.ONE.shiftLeft(128));
+ }
+ Assertions.assertEquals(expected, x.subtract(y).toBigInteger());
+ }
+
+ static Stream<Arguments> testSubtract() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ final long d = rng.nextLong();
+ builder.accept(Arguments.of(a, b, c, d));
+ builder.accept(Arguments.of(0, 0, c, d));
+ builder.accept(Arguments.of(-1L, -1L, c, d));
+ }
+ builder.accept(Arguments.of(-1L, -1L, -1L, -1L));
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192Test.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192Test.java
new file mode 100644
index 0000000..7f8d124
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt192Test.java
@@ -0,0 +1,206 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt192}.
+ */
+class UInt192Test {
+ @Test
+ void testCreate() {
+ final UInt192 v = UInt192.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddSquareLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).pow(2)
+ .add(BigInteger.valueOf(b).pow(2));
+ final UInt192 v = UInt192.create();
+ v.addSquare(a);
+ v.addSquare(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddSquareLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 61278342166787978L, 42, 8652939272947492397L};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddSquareLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .map(x -> x.pow(2))
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt192 v = UInt192.create();
+ for (final long x : a) {
+ v.addSquare(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), v.toDD(), 0x1.0p-106, "DD");
+ Assertions.assertEquals(expected.doubleValue(), v.toDouble(), "double");
+ }
+
+ static Stream<Arguments> testAddSquareLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt192(long a, long b, long c, long d, long e, long f) {
+ final UInt192 x = new UInt192(a, b, c);
+ final UInt192 y = new UInt192(d, e, f);
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 192-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^192).
+ if (expected.testBit(192)) {
+ expected = expected.flipBit(192);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d, %d) + (%d, %d, %d)", a, b, c, d, e, f));
+ // Check floating-point representation
+ TestUtils.assertEquals(new BigDecimal(expected), x.toDD(), 0x1.0p-106, "DD");
+ Assertions.assertEquals(expected.doubleValue(), x.toDouble(), "double");
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(192)) {
+ expected = expected.flipBit(192);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d, %d) self-addition", d, e, f));
+ }
+
+ static Stream<Arguments> testAddInt192() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextLong(), rng.nextLong(),
+ rng.nextLong() >>> 2, rng.nextLong(), rng.nextLong()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextLong(), rng.nextLong(),
+ rng.nextLong(), rng.nextLong(), rng.nextLong()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testMultiplyInt(long a, long b, long c, int n) {
+ assertMultiplyInt(a, b, c, n);
+ assertMultiplyInt(a >>> 32, b, c, n);
+ assertMultiplyInt(0, b, c, n);
+ }
+
+ private static void assertMultiplyInt(long a, long b, long c, int n) {
+ final UInt192 v = new UInt192(a, b, c);
+ BigInteger expected = v.toBigInteger().multiply(BigInteger.valueOf(n & 0xffff_ffffL));
+ // Clip to 192-bits. Only required if the upper 32-bits are non-zero.
+ final int len = expected.bitLength();
+ if (len > 192 && v.hi32() != 0) {
+ expected = expected.subtract(expected.shiftRight(192).shiftLeft(192));
+ }
+ Assertions.assertEquals(expected, v.unsignedMultiply(n).toBigInteger());
+ }
+
+ static Stream<Arguments> testMultiplyInt() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ final int[] x = {0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE};
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ for (final int n : x) {
+ builder.accept(Arguments.of(a, b, c, n));
+ }
+ for (int j = 0; j < 5; j++) {
+ builder.accept(Arguments.of(a, b, c, rng.nextInt()));
+ }
+ }
+ builder.accept(Arguments.of(-1L >>> 32, -1L, -1L, -1));
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testSubtract(long a, long b, long c, long d, long e) {
+ assertSubtract(a, b, c, d, e);
+ }
+
+ private static void assertSubtract(long a, long b, long c, long d, long e) {
+ final UInt192 x = new UInt192(a, b, c);
+ final UInt128 y = new UInt128(d, e);
+ BigInteger expected = x.toBigInteger().subtract(y.toBigInteger());
+ if (expected.signum() < 0) {
+ expected = expected.add(BigInteger.ONE.shiftLeft(192));
+ }
+ Assertions.assertEquals(expected, x.subtract(y).toBigInteger());
+ }
+
+ static Stream<Arguments> testSubtract() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ final long a = rng.nextLong();
+ final long b = rng.nextLong();
+ final long c = rng.nextLong();
+ final long d = rng.nextLong();
+ final long e = rng.nextLong();
+ builder.accept(Arguments.of(a, b, c, d, e));
+ builder.accept(Arguments.of(0, 0, 0, d, e));
+ builder.accept(Arguments.of(-1L, -1L, -1L, d, e));
+ }
+ builder.accept(Arguments.of(-1L, -1L, -1L, -1L, -1L));
+ return builder.build();
+ }
+}
diff --git a/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96Test.java b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96Test.java
new file mode 100644
index 0000000..57b142c
--- /dev/null
+++ b/commons-statistics-examples/examples-jmh/src/test/java/org/apache/commons/statistics/examples/jmh/descriptive/UInt96Test.java
@@ -0,0 +1,150 @@
+/*
+ * 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.examples.jmh.descriptive;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.stream.Stream;
+import org.apache.commons.numbers.core.DD;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test for {@link UInt96}.
+ */
+class UInt96Test {
+ @Test
+ void testCreate() {
+ final UInt96 v = UInt96.create();
+ Assertions.assertEquals(BigInteger.ZERO, v.toBigInteger());
+ }
+
+ @Test
+ void testAddLongMinValue() {
+ final UInt96 v = UInt96.of(5675757768682342956L);
+ final BigInteger x = BigInteger.ONE.shiftLeft(63);
+ BigInteger expected = v.toBigInteger();
+ for (int i = 1; i <= 5; i++) {
+ // Accepts a negative value without exception. This is
+ // computed correctly if the current low 32 bits
+ // added to the argument do not overflow. This is always
+ // true for min value as all lower 32-bits are zero.
+ v.addPositive(Long.MIN_VALUE);
+ expected = expected.add(x);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLong(long a, long b) {
+ final BigInteger expected = BigInteger.valueOf(a).add(BigInteger.valueOf(b));
+ final UInt96 v = UInt96.of(a);
+ v.addPositive(b);
+ Assertions.assertEquals(expected, v.toBigInteger());
+ }
+
+ static Stream<Arguments> testAddLong() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final long[] x = {0, 1, Long.MAX_VALUE, 612783421678L, 42};
+ for (final long i : x) {
+ for (final long j : x) {
+ builder.accept(Arguments.of(i, j));
+ }
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddLongs(long[] a) {
+ final BigInteger expected = Arrays.stream(a).mapToObj(BigInteger::valueOf)
+ .reduce(BigInteger::add).orElse(BigInteger.ZERO);
+ final UInt96 v = UInt96.create();
+ for (final long x : a) {
+ Assertions.assertFalse(x < 0, "Value must be positive");
+ v.addPositive(x);
+ }
+ Assertions.assertEquals(expected, v.toBigInteger());
+ // Check floating-point representation
+ Assertions.assertEquals(
+ DD.from(new BigDecimal(expected)),
+ v.toDD());
+ }
+
+ static Stream<Arguments> testAddLongs() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (final int n : new int[] {50, 100}) {
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 1).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 2).toArray()));
+ builder.accept(Arguments.of(rng.longs(n).map(x -> x >>> 4).toArray()));
+ }
+ return builder.build();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void testAddInt128(long a, int b, long c, int d) {
+ final UInt96 x = new UInt96(a, b);
+ final UInt96 y = new UInt96(c, d);
+ Assertions.assertEquals(a, x.hi64());
+ Assertions.assertEquals(b, x.lo32());
+ BigInteger expected = x.toBigInteger().add(y.toBigInteger());
+ // The result is an unsigned 96-bit integer.
+ // This is subject to integer overflow.
+ // Clip the unlimited BigInteger result to the range [0, 2^96).
+ if (expected.testBit(96)) {
+ expected = expected.flipBit(96);
+ }
+ x.add(y);
+ Assertions.assertEquals(expected, x.toBigInteger(),
+ () -> String.format("(%d, %d) + (%d, %d)", a, b, c, d));
+ // Check floating-point representation
+ Assertions.assertEquals(
+ DD.from(new BigDecimal(expected)),
+ x.toDD());
+ // Check self-addition
+ expected = y.toBigInteger();
+ expected = expected.add(expected);
+ if (expected.testBit(96)) {
+ expected = expected.flipBit(96);
+ }
+ y.add(y);
+ Assertions.assertEquals(expected, y.toBigInteger(),
+ () -> String.format("(%d, %d) self-addition", c, d));
+ }
+
+ static Stream<Arguments> testAddInt128() {
+ final Stream.Builder<Arguments> builder = Stream.builder();
+ final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
+ for (int i = 0; i < 50; i++) {
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextInt(), rng.nextLong() >>> 2, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 2, rng.nextInt(), rng.nextLong() >>> 1, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong() >>> 1, rng.nextInt(), rng.nextLong() >>> 2, rng.nextInt()));
+ builder.accept(Arguments.of(rng.nextLong(), rng.nextInt(), rng.nextLong(), rng.nextInt()));
+ }
+ return builder.build();
+ }
+}
diff --git a/src/conf/checkstyle/checkstyle-suppressions.xml b/src/conf/checkstyle/checkstyle-suppressions.xml
index 8216ef6..c363f46 100644
--- a/src/conf/checkstyle/checkstyle-suppressions.xml
+++ b/src/conf/checkstyle/checkstyle-suppressions.xml
@@ -41,4 +41,5 @@
<suppress checks="MethodLength" files=".*[/\\]WilcoxonSignedRankTestTest.java" />
<suppress checks="IllegalCatch" files=".*[/\\]TestHelper.java" lines="390-450" />
<suppress checks="IllegalCatch" files=".*[/\\]BaseStatisticTest.java" lines="280-400" />
+ <suppress checks="IllegalCatch" files=".*[/\\]IntMathTest.java" lines="165-175" />
</suppressions>