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.core.source32;
18  
19  import java.util.stream.Stream;
20  import org.apache.commons.rng.LongJumpableUniformRandomProvider;
21  import org.apache.commons.rng.SplittableUniformRandomProvider;
22  import org.apache.commons.rng.UniformRandomProvider;
23  import org.apache.commons.rng.core.RandomAssert;
24  import org.junit.jupiter.api.Test;
25  import org.junit.jupiter.params.ParameterizedTest;
26  import org.junit.jupiter.params.provider.Arguments;
27  import org.junit.jupiter.params.provider.MethodSource;
28  
29  /**
30   * Test for {@link L32X64Mix}.
31   */
32  class L32X64MixTest extends AbstractLXMTest {
33  
34      /**
35       * Factory to create a composite LXM generator that is equivalent
36       * to the RNG under test.
37       */
38      private static class Factory implements LXMGeneratorFactory {
39          static final Factory INSTANCE = new Factory();
40  
41          @Override
42          public int lcgSeedSize() {
43              return 2;
44          }
45  
46          @Override
47          public int xbgSeedSize() {
48              return 2;
49          }
50  
51          @Override
52          public LXMGenerator create(int[] seed) {
53              return new LXMGenerator(getMix(),
54                                      new LCG32(seed[0], seed[1]),
55                                      new XBGXoRoShiRo64(seed[2], seed[3]));
56          }
57      }
58  
59      @Override
60      LXMGeneratorFactory getFactory() {
61          return Factory.INSTANCE;
62      }
63  
64      @Override
65      LongJumpableUniformRandomProvider create(int[] seed) {
66          return new L32X64Mix(seed);
67      }
68  
69      @Override
70      Stream<Arguments> getReferenceData() {
71          /*
72           * Reference data from JDK 17:
73           * java.util.random.RandomGeneratorFactory.of("L32X64MixRandom").create(seed)
74           *
75           * Full byte[] seed created using SecureRandom.nextBytes. The seed was converted
76           * to int[] by filling the long bits sequentially starting at the most
77           * significant byte matching the method used by JDK 17. A sign extension bug in
78           * byte[] conversion required all byte indices (i % 4 != 0) to be non-negative.
79           * See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8282144
80           *
81           * Note: Seed order: LCG addition; LCG state; XBG state.
82           */
83          return Stream.of(
84              Arguments.of(
85                  new int[] {
86                      0x5a16253f, 0xd449657e, 0x5b46012d, 0x1d504d64,
87                  },
88                  new int[] {
89                      0xa6cc8f9b, 0xb9d3f0e3, 0xb8861d42, 0x9f8001a2,
90                      0xaf1eea5b, 0x4e3bc947, 0x1c6378b8, 0x54ccc942,
91                      0x4629da71, 0x0126a7e7, 0x6038f89c, 0xff72cb5c,
92                      0x3853ae58, 0xc86ccf16, 0x02d32147, 0x78afed22,
93                      0x432194a4, 0xf79809aa, 0x8c115507, 0x646b0445,
94                      0xed63cc9c, 0x4e1d7b55, 0x699cf21d, 0x7d2adf74,
95                      0xc2613634, 0x5ac9c96f, 0x1f2e59d2, 0x4c9878fb,
96                      0x49c7a137, 0xd277cdf8, 0x369dd190, 0x1edeb2dc,
97                      0x10bd09bd, 0x86e5140c, 0xe6bcb04c, 0x67f97775,
98                      0x8f979b43, 0x36dfa266, 0x3acf5157, 0xe69aac08,
99                  }),
100             Arguments.of(
101                 new int[] {
102                     0xa3512c5b, 0x970f3100, 0xfc22313b, 0xa71f183a,
103                 },
104                 new int[] {
105                     0x971dc6d7, 0xd8200b05, 0x0093d467, 0x74f11b96,
106                     0x8945aa8f, 0x88a3d635, 0xb1fcb212, 0x66ed8543,
107                     0xd5970056, 0x7b8b2d89, 0xd2d3957b, 0xc4ecc777,
108                     0xf8e295c3, 0x4ffb4a29, 0x509fc6b0, 0x9c5b1965,
109                     0x5008c757, 0x3a19410a, 0xc3992498, 0x0c4a8f22,
110                     0x3f6abfd8, 0xe943ac45, 0xb632fcd3, 0x3d1f3201,
111                     0xb17bc9f5, 0x902a9b9c, 0x39a76388, 0xd8c856a0,
112                     0x7422cf86, 0x3f776592, 0xef70a122, 0x053b628e,
113                     0x0dec8c4e, 0x5b0d80c9, 0xf58d19fc, 0x741f1361,
114                     0x01b747f7, 0x6b325a32, 0x714ed761, 0xf1facf8e,
115                 }),
116             Arguments.of(
117                 new int[] {
118                     0x17767167, 0x1b6e286c, 0xe84a2f12, 0x637a5034,
119                 },
120                 new int[] {
121                     0xd69fd4c1, 0x134945f8, 0x34c2fd4e, 0xd152b08f,
122                     0xb5a7e2bd, 0xd8afab0d, 0xed15f9d2, 0x6baba06e,
123                     0x9a1e8bcd, 0x8b392fbd, 0xb1e1cad9, 0x929f06d9,
124                     0xc9467860, 0x3d492690, 0x84fd7d06, 0xb576b0f9,
125                     0x860eac4c, 0xd3b7aa5b, 0x3ff4dd13, 0xfea00d01,
126                     0x025143a0, 0xf1c5885a, 0x3041a8be, 0xbfbfb908,
127                     0x186cd9cc, 0x1771875b, 0x9cc65fe2, 0xc9189702,
128                     0x6063b09d, 0x20f0286b, 0x6094b83d, 0x8c3fe8f5,
129                     0x2bf720eb, 0xe229e207, 0xe33f93db, 0x443b3849,
130                     0x79cc70a8, 0xcbf0de84, 0xe57c6367, 0xe555ee21,
131                 }));
132     }
133 
134     @ParameterizedTest
135     @MethodSource(value = "getReferenceData")
136     void testElementConstructor(int[] seed, int[] expected) {
137         final L32X64Mix rng1 = new L32X64Mix(seed);
138         final L32X64Mix rng2 = new L32X64Mix(seed[0], seed[1], seed[2], seed[3]);
139         RandomAssert.assertNextLongEquals(seed.length * 2, rng1, rng2);
140     }
141 
142     /**
143      * Test split with zero bits from the source. This should be robust to escape the state
144      * of all zero bits that will create an invalid state for the xor-based generator (XBG).
145      */
146     @Test
147     void testSplitWithZeroBits() {
148         final UniformRandomProvider zeroSource = () -> 0;
149         final int[] seed = new int[Factory.INSTANCE.seedSize()];
150         // Here we copy the split which sets the LCG increment to odd
151         seed[(Factory.INSTANCE.lcgSeedSize() / 2) - 1] = 1;
152         final SplittableUniformRandomProvider rng1 = new L32X64Mix(seed);
153         final SplittableUniformRandomProvider rng2 = rng1.split(zeroSource);
154         RandomAssert.assertNextIntNotEquals(seed.length * 2, rng1, rng2);
155 
156         // Since we know how the zero seed is amended
157         int z = 0;
158         for (int i = Factory.INSTANCE.lcgSeedSize(); i < seed.length; i++) {
159             seed[i] = LXMSupport.lea32(z);
160             z += LXMSupport.GOLDEN_RATIO_32;
161         }
162         final SplittableUniformRandomProvider rng3 = new L32X64Mix(seed);
163         final SplittableUniformRandomProvider rng4 = rng1.split(zeroSource);
164         RandomAssert.assertNextIntEquals(seed.length * 2, rng3, rng4);
165     }
166 }