Contains methods that use the {@code java.lang.invoke} package to call + * {@code java.lang.Math} functions for computing the high part of the 128-bit + * result of a multiply of two 64-bit longs. These methods may be supported + * by intrinsic calls to native operations if supported on the platform for + * a significant performance gain. + * + *
Note + * + *
This class is used specifically in the {@link Philox4x64} generator which + * has a state update cycle which is performance dependent on the multiply + * of two unsigned long values. Other classes which use unsigned multiply + * and are not performance dependent on the method do not use this implementation + * (for example the LXM family of generators). This allows the multiply method + * to be adapted to the usage of {@link Philox4x64} which always has the first + * argument as a negative constant. + * + * @since 1.7 + */ +final class PhiloxSupport { + /** + * Method to compute unsigned multiply high. Uses: + *
The look-up assumes the named method accepts two long arguments and returns + * a long. + * + * @param methodName Method name. + * @return the method + * @throws NoSuchMethodException if the method does not exist + * @throws IllegalAccessException if the method cannot be accessed + */ + static MethodHandle getMathMethod(String methodName) throws NoSuchMethodException, IllegalAccessException { + return MethodHandles.publicLookup() + .findStatic(Math.class, + methodName, + MethodType.methodType(long.class, long.class, long.class)); + } + + /** + * Test the implementation of unsigned multiply high. + * It is assumed the invocation of the method may raise an {@link IllegalStateException} + * if it cannot be invoked. + * + * @param op Method implementation. + * @return True if the method can be called to generate the expected result + */ + static boolean testUnsignedMultiplyHigh(LongBinaryOperator op) { + try { + // Test with a signed input to the multiplication. + // The result is: (1L << 63) * 2 == 1LL << 64 + return op != null && op.applyAsLong(Long.MIN_VALUE, 2L) == 1; + } catch (IllegalStateException ignored) { + return false; + } + } + + /** + * Multiply the two values as if unsigned 64-bit longs to produce the high 64-bits + * of the 128-bit unsigned result. The first argument is assumed to be negative. + * + *
This method uses a {@link MethodHandle} to call Java functions added since + * Java 8 to the {@link Math} class: + *
Warning + * + *
For performance reasons this method assumes the first argument is negative.
+ * This allows some operations to be dropped if running on Java 9 to 17.
+ *
+ * @param value1 the first value (must be negative)
+ * @param value2 the second value
+ * @return the high 64-bits of the 128-bit result
+ */
+ static long unsignedMultiplyHigh(long value1, long value2) {
+ return UNSIGNED_MULTIPLY_HIGH.applyAsLong(value1, value2);
+ }
+}
diff --git a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LXMSupportTest.java b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LXMSupportTest.java
index 8a8138cf5..6da9258cd 100644
--- a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LXMSupportTest.java
+++ b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/LXMSupportTest.java
@@ -108,7 +108,7 @@ void testUnsignedMultiplyHigh() {
}
}
- private static void assertMultiplyHigh(long v1, long v2, long hi) {
+ static void assertMultiplyHigh(long v1, long v2, long hi) {
final BigInteger bi1 = toUnsignedBigInteger(v1);
final BigInteger bi2 = toUnsignedBigInteger(v2);
final BigInteger expected = bi1.multiply(bi2);
diff --git a/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/PhiloxSupportTest.java b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/PhiloxSupportTest.java
new file mode 100644
index 000000000..cbccb85a8
--- /dev/null
+++ b/commons-rng-core/src/test/java/org/apache/commons/rng/core/source64/PhiloxSupportTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.rng.core.source64;
+
+import java.util.SplittableRandom;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link PhiloxSupport}.
+ */
+class PhiloxSupportTest {
+ @Test
+ void testGetMathMethod() throws NoSuchMethodException, IllegalAccessException {
+ // Java 8 method: Math.addExact(long, long)
+ Assertions.assertNotNull(PhiloxSupport.getMathMethod("addExact"));
+ Assertions.assertThrows(NoSuchMethodException.class, () -> PhiloxSupport.getMathMethod("foo"));
+ }
+
+ @Test
+ void testTestUnsignedMultiplyHigh() {
+ Assertions.assertTrue(PhiloxSupport.testUnsignedMultiplyHigh(LXMSupport::unsignedMultiplyHigh));
+ // Test all code paths
+ Assertions.assertFalse(PhiloxSupport.testUnsignedMultiplyHigh(null), "Null operator");
+ Assertions.assertFalse(PhiloxSupport.testUnsignedMultiplyHigh((a, b) -> 0), "Invalid multiply operator");
+ Assertions.assertFalse(PhiloxSupport.testUnsignedMultiplyHigh((a, b) -> {
+ throw new IllegalStateException();
+ }), "Illegal call to operator");
+ }
+
+ @Test
+ void testUnsignedMultiplyHighEdgeCases() {
+ 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,
+ // Philox 4x64 multiplication constants
+ 0xD2E7470EE14C6C93L, 0xCA5A826395121157L,
+ };
+
+ for (final long v1 : values) {
+ // Must be odd
+ if (v1 >= 0) {
+ continue;
+ }
+ for (final long v2 : values) {
+ LXMSupportTest.assertMultiplyHigh(v1, v2, PhiloxSupport.unsignedMultiplyHigh(v1, v2));
+ }
+ }
+ }
+
+ @Test
+ void testUnsignedMultiplyHigh() {
+ final long[] values = new SplittableRandom().longs(100).toArray();
+ for (long v1 : values) {
+ // Must be odd
+ v1 |= Long.MIN_VALUE;
+ for (final long v2 : values) {
+ LXMSupportTest.assertMultiplyHigh(v1, v2, PhiloxSupport.unsignedMultiplyHigh(v1, v2));
+ }
+ }
+ }
+}
diff --git a/src/conf/checkstyle/checkstyle-suppressions.xml b/src/conf/checkstyle/checkstyle-suppressions.xml
index f9c106c7d..fe523ec80 100644
--- a/src/conf/checkstyle/checkstyle-suppressions.xml
+++ b/src/conf/checkstyle/checkstyle-suppressions.xml
@@ -26,6 +26,8 @@