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 b58bd961ddbb0d2f1ce05fd28516fae2bf0160db Author: Alex Herbert <aherb...@apache.org> AuthorDate: Tue Mar 25 12:44:50 2025 +0000 Sonar fix: encapsulate division logic in Int128 --- .../commons/statistics/descriptive/Int128.java | 18 +++++++++++++ .../commons/statistics/descriptive/IntMath.java | 20 -------------- .../commons/statistics/descriptive/IntMean.java | 2 +- .../commons/statistics/descriptive/LongMean.java | 2 +- .../commons/statistics/descriptive/Int128Test.java | 29 ++++++++++++++++++++ .../statistics/descriptive/IntMathTest.java | 31 ---------------------- 6 files changed, 49 insertions(+), 53 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 bf0b99e..b006ea1 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 @@ -38,6 +38,8 @@ import org.apache.commons.numbers.core.DD; final class Int128 { /** Mask for the lower 32-bits of a long. */ private static final long MASK32 = 0xffff_ffffL; + /** 2^53. */ + private static final long TWO_POW_53 = 1L << 53; /** low 64-bits. */ private long lo; @@ -249,6 +251,22 @@ final class Int128 { return DD.of(lo).add((hi & MASK32) * 0x1.0p64).add((hi >> Integer.SIZE) * 0x1.0p96); } + /** + * Divide by the count {@code n}, returning the value as a {@code double}. + * + * @param n Count. + * @return the quotient + */ + double divideToDouble(long n) { + final DD a = 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(); + } + /** * Convert to an {@code int}; throwing an exception if the value overflows an {@code int}. * 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 fd5c164..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 @@ -18,7 +18,6 @@ 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. @@ -36,8 +35,6 @@ 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() {} @@ -391,21 +388,4 @@ 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 5987571..9fcbff6 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 @@ -180,7 +180,7 @@ public final class IntMean implements IntStatistic, StatisticAccumulator<IntMean return (double) sum.lo64() / n; } // Extended precision - return IntMath.divide(sum, n); + return sum.divideToDouble(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 492ed78..305b1ba 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 @@ -177,7 +177,7 @@ public final class LongMean implements LongStatistic, StatisticAccumulator<LongM return (double) sum.lo64() / n; } // Extended precision - return IntMath.divide(sum, n); + return sum.divideToDouble(n); } @Override 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 b445474..8f884ce 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 @@ -19,11 +19,14 @@ 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.LongStream; import java.util.stream.Stream; import org.apache.commons.rng.UniformRandomProvider; import org.apache.commons.rng.simple.RandomSource; +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; @@ -179,6 +182,32 @@ class Int128Test { return builder.build(); } + @ParameterizedTest + @MethodSource + void testDivideToDouble(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, x.divideToDouble(n), DoubleTolerances.equals(), + () -> String.format("(2^64 * %d + %d) / %d; Seed=%s", + a, b, n, Arrays.toString(TestHelper.createRNGSeed()))); + } + + static Stream<Arguments> testDivideToDouble() { + 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(); + } + @ParameterizedTest @ValueSource(ints = {Integer.MAX_VALUE, Integer.MIN_VALUE}) void testToIntExact(int x) { 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 f6efb10..72a298f 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,18 +17,13 @@ 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; @@ -208,30 +203,4 @@ 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(); - } }