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 dec22784a51aeb0899750a69f81736f2bde0f001 Author: Alex Herbert <aherb...@apache.org> AuthorDate: Wed Dec 27 22:59:42 2023 +0000 Refactor division for the integer mean --- .../commons/statistics/descriptive/IntMath.java | 20 ++++++++++++++ .../commons/statistics/descriptive/IntMean.java | 5 ++-- .../commons/statistics/descriptive/LongMean.java | 5 ++-- .../statistics/descriptive/IntMathTest.java | 31 ++++++++++++++++++++++ .../commons/statistics/descriptive/TestHelper.java | 2 ++ src/conf/checkstyle/checkstyle-suppressions.xml | 2 +- 6 files changed, 58 insertions(+), 7 deletions(-) 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 2309750..fd5c164 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 @@ -18,6 +18,7 @@ package org.apache.commons.statistics.descriptive; import java.math.BigDecimal; import java.math.BigInteger; +import org.apache.commons.numbers.core.DD; /** * Support class for integer math. @@ -35,6 +36,8 @@ final class IntMath { private static final int EXP_SHIFT = 52; /** 0.5. */ private static final double HALF = 0.5; + /** 2^53. */ + private static final long TWO_POW_53 = 1L << 53; /** No instances. */ private IntMath() {} @@ -388,4 +391,21 @@ final class IntMath { } return y; } + + /** + * Divide value {@code x} by the count {@code n}. + * + * @param x Value. + * @param n Count. + * @return the quotient + */ + static double divide(Int128 x, long n) { + final DD a = x.toDD(); + if (n < TWO_POW_53) { + // n is a representable double + return a.divide(n).doubleValue(); + } + // Extended precision divide when n > 2^53 + return a.divide(DD.of(n)).doubleValue(); + } } 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 index 0713047..9bd384d 100644 --- 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 @@ -130,9 +130,8 @@ public final class IntMean implements IntStatistic, StatisticAccumulator<IntMean 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(); + // Extended precision + return IntMath.divide(sum, n); } @Override 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 index 3f84a9e..37b2095 100644 --- 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 @@ -127,9 +127,8 @@ public final class LongMean implements LongStatistic, StatisticAccumulator<LongM 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(); + // Extended precision + return IntMath.divide(sum, n); } @Override 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 ccc4416..b16baff 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 @@ -17,13 +17,18 @@ 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.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.statistics.distribution.DoubleTolerances; +import org.apache.commons.statistics.distribution.TestUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -203,4 +208,30 @@ class IntMathTest { Assertions.assertEquals(0, Math.toIntExact(Math.round(x))); Assertions.assertThrowsExactly(ArithmeticException.class, () -> IntMath.toIntExact(x)); } + + @ParameterizedTest + @MethodSource + void testInt128Divide(long a, long b, long n) { + final Int128 x = new Int128(a, b); + final BigInteger bi = BigInteger.valueOf(a).shiftLeft(Long.SIZE).add(BigInteger.valueOf(b)); + final double expected = new BigDecimal(bi).divide( + new BigDecimal(n), MathContext.DECIMAL128).doubleValue(); + // This may require a 1 ulp tolerance; log the seed to allow repeats + TestUtils.assertEquals(expected, IntMath.divide(x, n), DoubleTolerances.equals(), + () -> String.format("(2^64 * %d + %d) / %d; Seed=%s", + a, b, n, Arrays.toString(TestHelper.createRNGSeed()))); + } + + static Stream<Arguments> testInt128Divide() { + final UniformRandomProvider rng = TestHelper.createRNG(TestHelper.createRNGSeed()); + final Stream.Builder<Arguments> builder = Stream.builder(); + for (int i = 0; i < 100; i++) { + final long a = rng.nextLong(); + final long b = rng.nextLong(); + final long n = rng.nextLong(); + builder.accept(Arguments.of(a, b, n >>> 1)); + builder.accept(Arguments.of(a, b, n >>> 43)); + } + return builder.build(); + } } 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 22c385d..a1a57e5 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 @@ -218,6 +218,8 @@ final class TestHelper { /** * Creates a seed for the RNG. + * This is the same seed within an invocation of the JVM; seeds will be different + * across JVM instances. * * @return the seed * @see #createRNG(long[]) diff --git a/src/conf/checkstyle/checkstyle-suppressions.xml b/src/conf/checkstyle/checkstyle-suppressions.xml index c363f46..dd425bc 100644 --- a/src/conf/checkstyle/checkstyle-suppressions.xml +++ b/src/conf/checkstyle/checkstyle-suppressions.xml @@ -41,5 +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" /> + <suppress checks="IllegalCatch" files=".*[/\\]IntMathTest.java" lines="170-185" /> </suppressions>