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