blob: a80d6e51e68f3abcfc197bce15599ef0d4493d91 [file] [log] [blame]
/*
* 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.examples.jmh.core;
import java.math.BigInteger;
import java.util.function.LongSupplier;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.LCG128Source;
import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.LXM128Source;
import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.UnsignedMultiply128Source;
import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.UnsignedMultiplyHighSource;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.opentest4j.TestAbortedException;
/**
* Test for methods used in {@link LXMBenchmark}. This checks the different versions of
* 128-bit multiplication compute the correct result (verified using BigInteger).
*/
class LXMBenchmarkTest {
/** 2^63. */
private static final BigInteger TWO_POW_63 = BigInteger.ONE.shiftLeft(63);
/**
* A function operating on 2 long values.
*/
interface LongLongFunction {
/**
* Apply the function.
*
* @param a Argument a
* @param b Argument b
* @return the result
*/
long apply(long a, long b);
}
/**
* A function operating on 4 long values.
*/
interface LongLongLongLongFunction {
/**
* Apply the function.
*
* @param a Argument a
* @param b Argument b
* @param c Argument c
* @param d Argument d
* @return the result
*/
long apply(long a, long b, long c, long d);
}
@Test
@EnabledForJreRange(min = JRE.JAVA_9)
void testMathMultiplyHigh() {
try {
assertUnsignedMultiply(UnsignedMultiplyHighSource::mathMultiplyHigh, null);
} catch (NoSuchMethodError e) {
throw new TestAbortedException("Update mathMultiplyHigh to call Math.multiplyHigh");
}
}
@Test
// Note: JAVA_18 is not in the enum
@EnabledForJreRange(min = JRE.OTHER)
void testMathUnsignedMultiplyHigh() {
try {
assertUnsignedMultiply(UnsignedMultiplyHighSource::mathUnsignedMultiplyHigh, null);
} catch (NoSuchMethodError e) {
throw new TestAbortedException("Update mathUnsignedMultiplyHigh to call Math.unsignedMultiplyHigh");
}
}
@Test
void testUnsignedMultiplyHigh() {
assertUnsignedMultiply(UnsignedMultiplyHighSource::unsignedMultiplyHigh, null);
}
@Test
void testUnsignedMultiplyHighWithLow() {
final long[] lo = {0};
assertUnsignedMultiply((a, b) -> UnsignedMultiplyHighSource.unsignedMultiplyHigh(a, b, lo), lo);
}
private static void assertUnsignedMultiply(LongLongFunction fun, long[] lo) {
final UniformRandomProvider rng = RandomSource.JSF_64.create();
for (int i = 0; i < 100; i++) {
final long a = rng.nextLong();
final long b = rng.nextLong();
Assertions.assertEquals(unsignedMultiplyHigh(a, b),
fun.apply(a, b));
if (lo != null) {
Assertions.assertEquals(a * b, lo[0]);
}
}
}
@Test
void testUnsignedMultiplyHighML() {
final UniformRandomProvider rng = RandomSource.JSF_64.create();
final long b = LXMBenchmark.ML;
for (int i = 0; i < 100; i++) {
final long a = rng.nextLong();
Assertions.assertEquals(unsignedMultiplyHigh(a, b),
UnsignedMultiplyHighSource.unsignedMultiplyHighML(a));
}
}
@Test
void testUnsignedMultiplyHighWithProducts() {
assertUnsignedMultiply128((a, b, c, d) ->
UnsignedMultiplyHighSource.unsignedMultiplyHigh(b, d) +
a * d + b * c, null);
}
@Test
void testUnsignedMultiply128() {
final long[] lo = {0};
assertUnsignedMultiply128((a, b, c, d) -> UnsignedMultiply128Source.unsignedMultiply128(a, b, c, d, lo), lo);
}
private static void assertUnsignedMultiply128(LongLongLongLongFunction fun, long[] lo) {
final UniformRandomProvider rng = RandomSource.JSF_64.create();
for (int i = 0; i < 100; i++) {
final long a = rng.nextLong();
final long b = rng.nextLong();
final long c = rng.nextLong();
final long d = rng.nextLong();
Assertions.assertEquals(unsignedMultiplyHigh(a, b, c, d),
fun.apply(a, b, c, d));
if (lo != null) {
Assertions.assertEquals(b * d, lo[0]);
}
}
}
@Test
void testUnsignedSquareHigh() {
final UniformRandomProvider rng = RandomSource.JSF_64.create();
for (int i = 0; i < 100; i++) {
final long a = rng.nextLong();
Assertions.assertEquals(unsignedMultiplyHigh(a, a),
UnsignedMultiply128Source.unsignedSquareHigh(a));
}
}
@Test
void testUnsignedSquare128() {
final long[] lo = {0};
final UniformRandomProvider rng = RandomSource.JSF_64.create();
for (int i = 0; i < 100; i++) {
final long a = rng.nextLong();
final long b = rng.nextLong();
Assertions.assertEquals(unsignedMultiplyHigh(a, b, a, b),
UnsignedMultiply128Source.unsignedSquare128(a, b, lo));
Assertions.assertEquals(b * b, lo[0]);
}
}
/**
* Compute the unsigned multiply of two values using BigInteger.
*
* @param a First value
* @param b Second value
* @return the upper 64-bits of the 128-bit result
*/
private static long unsignedMultiplyHigh(long a, long b) {
final BigInteger ba = toUnsignedBigInteger(a);
final BigInteger bb = toUnsignedBigInteger(b);
return ba.multiply(bb).shiftRight(64).longValue();
}
/**
* Compute the unsigned multiply of two 128-bit values using BigInteger.
*
* <p>This computes the upper 64-bits of a 128-bit result that would
* be generated by multiplication to a native 128-bit integer type.
*
* @param a First value
* @param b Second value
* @return the upper 64-bits of the truncated 128-bit result
*/
private static long unsignedMultiplyHigh(long ah, long al, long bh, long bl) {
final BigInteger a = toUnsignedBigInteger(ah, al);
final BigInteger b = toUnsignedBigInteger(bh, bl);
return a.multiply(b).shiftRight(64).longValue();
}
/**
* 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);
}
/**
* Create a 128-bit big integer treating the value as unsigned.
*
* @param hi High part of value
* @param lo High part of value
* @return the big integer
*/
private static BigInteger toUnsignedBigInteger(long hi, long lo) {
return toUnsignedBigInteger(hi).shiftLeft(64).add(toUnsignedBigInteger(lo));
}
/**
* Test all 128-bit LCG implementations compute the same state update.
*/
@RepeatedTest(value = 50)
void testLcg128() {
final long ah = RandomSource.createLong();
final long al = RandomSource.createLong() | 1;
final LongSupplier a = new LCG128Source.ReferenceLcg128(ah, al)::getAsLong;
final LongSupplier[] b = new LongSupplier[] {
new LCG128Source.ReferenceLcg128(ah, al)::getAsLong,
new LCG128Source.ReferenceLcg128Final(ah, al)::getAsLong,
new LCG128Source.CompareUnsignedLcg128(ah, al)::getAsLong,
new LCG128Source.ConditionalLcg128(ah, al)::getAsLong,
new LCG128Source.ConditionalLcg128Final(ah, al)::getAsLong,
new LCG128Source.BranchlessLcg128(ah, al)::getAsLong,
new LCG128Source.BranchlessFullLcg128(ah, al)::getAsLong,
new LCG128Source.BranchlessFullComposedLcg128(ah, al)::getAsLong,
};
for (int i = 0; i < 10; i++) {
final long expected = a.getAsLong();
for (int j = 0; j < b.length; j++) {
Assertions.assertEquals(expected, b[j].getAsLong());
}
}
}
/**
* Test all 128-bit LCG based implementation of a LXM generator compute the same state update.
*/
@RepeatedTest(value = 50)
void testLxm128() {
final long ah = RandomSource.createLong();
final long al = RandomSource.createLong();
// Native seed: LCG add, LCG state, XBG
// This requires the initial state of the XBG and LCG.
final long[] seed = {ah, al,
LXM128Source.S0, LXM128Source.S1,
LXM128Source.X0, LXM128Source.X1};
final UniformRandomProvider rng = RandomSource.L128_X128_MIX.create(seed);
final LongSupplier a = rng::nextLong;
final LongSupplier[] b = new LongSupplier[] {
new LXM128Source.ReferenceLxm128(ah, al)::getAsLong,
new LXM128Source.BranchlessLxm128(ah, al)::getAsLong,
};
for (int i = 0; i < 10; i++) {
final long expected = a.getAsLong();
for (int j = 0; j < b.length; j++) {
Assertions.assertEquals(expected, b[j].getAsLong());
}
}
}
}