View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.examples.jmh.core;
18  
19  import java.math.BigInteger;
20  import java.util.function.LongSupplier;
21  import org.apache.commons.rng.UniformRandomProvider;
22  import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.LCG128Source;
23  import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.LXM128Source;
24  import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.UnsignedMultiply128Source;
25  import org.apache.commons.rng.examples.jmh.core.LXMBenchmark.UnsignedMultiplyHighSource;
26  import org.apache.commons.rng.simple.RandomSource;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.RepeatedTest;
29  import org.junit.jupiter.api.Test;
30  import org.junit.jupiter.api.condition.EnabledForJreRange;
31  import org.junit.jupiter.api.condition.JRE;
32  import org.opentest4j.TestAbortedException;
33  
34  /**
35   * Test for methods used in {@link LXMBenchmark}. This checks the different versions of
36   * 128-bit multiplication compute the correct result (verified using BigInteger).
37   */
38  class LXMBenchmarkTest {
39      /** 2^63. */
40      private static final BigInteger TWO_POW_63 = BigInteger.ONE.shiftLeft(63);
41  
42      /**
43       * A function operating on 2 long values.
44       */
45      interface LongLongFunction {
46          /**
47           * Apply the function.
48           *
49           * @param a Argument a
50           * @param b Argument b
51           * @return the result
52           */
53          long apply(long a, long b);
54      }
55  
56      /**
57       * A function operating on 4 long values.
58       */
59      interface LongLongLongLongFunction {
60          /**
61           * Apply the function.
62           *
63           * @param a Argument a
64           * @param b Argument b
65           * @param c Argument c
66           * @param d Argument d
67           * @return the result
68           */
69          long apply(long a, long b, long c, long d);
70      }
71  
72      @Test
73      @EnabledForJreRange(min = JRE.JAVA_9)
74      void testMathMultiplyHigh() {
75          try {
76              assertUnsignedMultiply(UnsignedMultiplyHighSource::mathMultiplyHigh, null);
77          } catch (NoSuchMethodError e) {
78              throw new TestAbortedException("Update mathMultiplyHigh to call Math.multiplyHigh");
79          }
80      }
81  
82      @Test
83      // Note: JAVA_18 is not in the enum
84      @EnabledForJreRange(min = JRE.OTHER)
85      void testMathUnsignedMultiplyHigh() {
86          try {
87              assertUnsignedMultiply(UnsignedMultiplyHighSource::mathUnsignedMultiplyHigh, null);
88          } catch (NoSuchMethodError e) {
89              throw new TestAbortedException("Update mathUnsignedMultiplyHigh to call Math.unsignedMultiplyHigh");
90          }
91      }
92  
93      @Test
94      void testUnsignedMultiplyHigh() {
95          assertUnsignedMultiply(UnsignedMultiplyHighSource::unsignedMultiplyHigh, null);
96      }
97  
98      @Test
99      void testUnsignedMultiplyHighWithLow() {
100         final long[] lo = {0};
101         assertUnsignedMultiply((a, b) -> UnsignedMultiplyHighSource.unsignedMultiplyHigh(a, b, lo), lo);
102     }
103 
104     private static void assertUnsignedMultiply(LongLongFunction fun, long[] lo) {
105         final UniformRandomProvider rng = RandomSource.JSF_64.create();
106         for (int i = 0; i < 100; i++) {
107             final long a = rng.nextLong();
108             final long b = rng.nextLong();
109             Assertions.assertEquals(unsignedMultiplyHigh(a, b),
110                                     fun.apply(a, b));
111             if (lo != null) {
112                 Assertions.assertEquals(a * b, lo[0]);
113             }
114         }
115     }
116 
117     @Test
118     void testUnsignedMultiplyHighML() {
119         final UniformRandomProvider rng = RandomSource.JSF_64.create();
120         final long b = LXMBenchmark.ML;
121         for (int i = 0; i < 100; i++) {
122             final long a = rng.nextLong();
123             Assertions.assertEquals(unsignedMultiplyHigh(a, b),
124                 UnsignedMultiplyHighSource.unsignedMultiplyHighML(a));
125         }
126     }
127 
128     @Test
129     void testUnsignedMultiplyHighWithProducts() {
130         assertUnsignedMultiply128((a, b, c, d) ->
131             UnsignedMultiplyHighSource.unsignedMultiplyHigh(b, d) +
132             a * d + b * c, null);
133     }
134 
135     @Test
136     void testUnsignedMultiply128() {
137         final long[] lo = {0};
138         assertUnsignedMultiply128((a, b, c, d) -> UnsignedMultiply128Source.unsignedMultiply128(a, b, c, d, lo), lo);
139     }
140 
141     private static void assertUnsignedMultiply128(LongLongLongLongFunction fun, long[] lo) {
142         final UniformRandomProvider rng = RandomSource.JSF_64.create();
143         for (int i = 0; i < 100; i++) {
144             final long a = rng.nextLong();
145             final long b = rng.nextLong();
146             final long c = rng.nextLong();
147             final long d = rng.nextLong();
148             Assertions.assertEquals(unsignedMultiplyHigh(a, b, c, d),
149                                     fun.apply(a, b, c, d));
150             if (lo != null) {
151                 Assertions.assertEquals(b * d, lo[0]);
152             }
153         }
154     }
155 
156     @Test
157     void testUnsignedSquareHigh() {
158         final UniformRandomProvider rng = RandomSource.JSF_64.create();
159         for (int i = 0; i < 100; i++) {
160             final long a = rng.nextLong();
161             Assertions.assertEquals(unsignedMultiplyHigh(a, a),
162                                     UnsignedMultiply128Source.unsignedSquareHigh(a));
163         }
164     }
165 
166     @Test
167     void testUnsignedSquare128() {
168         final long[] lo = {0};
169         final UniformRandomProvider rng = RandomSource.JSF_64.create();
170         for (int i = 0; i < 100; i++) {
171             final long a = rng.nextLong();
172             final long b = rng.nextLong();
173             Assertions.assertEquals(unsignedMultiplyHigh(a, b, a, b),
174                                     UnsignedMultiply128Source.unsignedSquare128(a, b, lo));
175             Assertions.assertEquals(b * b, lo[0]);
176         }
177     }
178 
179     /**
180      * Compute the unsigned multiply of two values using BigInteger.
181      *
182      * @param a First value
183      * @param b Second value
184      * @return the upper 64-bits of the 128-bit result
185      */
186     private static long unsignedMultiplyHigh(long a, long b) {
187         final BigInteger ba = toUnsignedBigInteger(a);
188         final BigInteger bb = toUnsignedBigInteger(b);
189         return ba.multiply(bb).shiftRight(64).longValue();
190     }
191 
192     /**
193      * Compute the unsigned multiply of two 128-bit values using BigInteger.
194      *
195      * <p>This computes the upper 64-bits of a 128-bit result that would
196      * be generated by multiplication to a native 128-bit integer type.
197      *
198      * @param a First value
199      * @param b Second value
200      * @return the upper 64-bits of the truncated 128-bit result
201      */
202     private static long unsignedMultiplyHigh(long ah, long al, long bh, long bl) {
203         final BigInteger a = toUnsignedBigInteger(ah, al);
204         final BigInteger b = toUnsignedBigInteger(bh, bl);
205         return a.multiply(b).shiftRight(64).longValue();
206     }
207 
208     /**
209      * Create a big integer treating the value as unsigned.
210      *
211      * @param v Value
212      * @return the big integer
213      */
214     private static BigInteger toUnsignedBigInteger(long v) {
215         return v < 0 ?
216             TWO_POW_63.add(BigInteger.valueOf(v & Long.MAX_VALUE)) :
217             BigInteger.valueOf(v);
218     }
219 
220     /**
221      * Create a 128-bit big integer treating the value as unsigned.
222      *
223      * @param hi High part of value
224      * @param lo High part of value
225      * @return the big integer
226      */
227     private static BigInteger toUnsignedBigInteger(long hi, long lo) {
228         return toUnsignedBigInteger(hi).shiftLeft(64).add(toUnsignedBigInteger(lo));
229     }
230 
231     /**
232      * Test all 128-bit LCG implementations compute the same state update.
233      */
234     @RepeatedTest(value = 50)
235     void testLcg128() {
236         final long ah = RandomSource.createLong();
237         final long al = RandomSource.createLong() | 1;
238         final LongSupplier a = new LCG128Source.ReferenceLcg128(ah, al)::getAsLong;
239         final LongSupplier[] b = new LongSupplier[] {
240             new LCG128Source.ReferenceLcg128(ah, al)::getAsLong,
241             new LCG128Source.ReferenceLcg128Final(ah, al)::getAsLong,
242             new LCG128Source.CompareUnsignedLcg128(ah, al)::getAsLong,
243             new LCG128Source.ConditionalLcg128(ah, al)::getAsLong,
244             new LCG128Source.ConditionalLcg128Final(ah, al)::getAsLong,
245             new LCG128Source.BranchlessLcg128(ah, al)::getAsLong,
246             new LCG128Source.BranchlessFullLcg128(ah, al)::getAsLong,
247             new LCG128Source.BranchlessFullComposedLcg128(ah, al)::getAsLong,
248         };
249         for (int i = 0; i < 10; i++) {
250             final long expected = a.getAsLong();
251             for (int j = 0; j < b.length; j++) {
252                 Assertions.assertEquals(expected, b[j].getAsLong());
253             }
254         }
255     }
256 
257     /**
258      * Test all 128-bit LCG based implementation of a LXM generator compute the same state update.
259      */
260     @RepeatedTest(value = 50)
261     void testLxm128() {
262         final long ah = RandomSource.createLong();
263         final long al = RandomSource.createLong();
264         // Native seed: LCG add, LCG state, XBG
265         // This requires the initial state of the XBG and LCG.
266         final long[] seed = {ah, al,
267                              LXM128Source.S0, LXM128Source.S1,
268                              LXM128Source.X0, LXM128Source.X1};
269         final UniformRandomProvider rng = RandomSource.L128_X128_MIX.create(seed);
270         final LongSupplier a = rng::nextLong;
271         final LongSupplier[] b = new LongSupplier[] {
272             new LXM128Source.ReferenceLxm128(ah, al)::getAsLong,
273             new LXM128Source.BranchlessLxm128(ah, al)::getAsLong,
274         };
275         for (int i = 0; i < 10; i++) {
276             final long expected = a.getAsLong();
277             for (int j = 0; j < b.length; j++) {
278                 Assertions.assertEquals(expected, b[j].getAsLong());
279             }
280         }
281     }
282 }