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  
18  package org.apache.commons.rng.simple.internal;
19  
20  import java.nio.ByteBuffer;
21  import java.nio.ByteOrder;
22  import java.util.Arrays;
23  import java.util.concurrent.ThreadLocalRandom;
24  import java.util.stream.IntStream;
25  import java.util.stream.LongStream;
26  import org.apache.commons.rng.core.source64.SplitMix64;
27  import org.apache.commons.rng.core.util.NumberFactory;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.RepeatedTest;
30  import org.junit.jupiter.api.Test;
31  import org.junit.jupiter.params.ParameterizedTest;
32  import org.junit.jupiter.params.provider.MethodSource;
33  import org.junit.jupiter.params.provider.ValueSource;
34  
35  /**
36   * Tests for {@link Conversions}.
37   */
38  class ConversionsTest {
39      /**
40       * The fractional part of the the golden ratio, phi, scaled to 64-bits and rounded to odd.
41       * <pre>
42       * phi = (sqrt(5) - 1) / 2) * 2^64
43       * </pre>
44       * @see <a href="https://en.wikipedia.org/wiki/Golden_ratio">Golden ratio</a>
45       */
46      private static final long GOLDEN_RATIO = 0x9e3779b97f4a7c15L;
47  
48      /**
49       * Gets the lengths for the byte[] seeds to convert.
50       *
51       * @return the lengths
52       */
53      static IntStream getByteLengths() {
54          return IntStream.rangeClosed(0, Long.BYTES * 2);
55      }
56  
57      /**
58       * Gets the lengths for the int[] seeds to convert.
59       *
60       * @return the lengths
61       */
62      static IntStream getIntLengths() {
63          return IntStream.rangeClosed(0, (Long.BYTES / Integer.BYTES) * 2);
64      }
65  
66      /**
67       * Gets the lengths for the long[] seeds to convert.
68       *
69       * @return the lengths
70       */
71      static IntStream getLongLengths() {
72          return IntStream.rangeClosed(0, 2);
73      }
74  
75      @ParameterizedTest
76      @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Integer.MAX_VALUE})
77      void testIntSizeFromByteSize(int size) {
78          Assertions.assertEquals((int) Math.ceil((double) size / Integer.BYTES), Conversions.intSizeFromByteSize(size));
79      }
80  
81      @ParameterizedTest
82      @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, Integer.MAX_VALUE})
83      void testLongSizeFromByteSize(int size) {
84          Assertions.assertEquals((int) Math.ceil((double) size / Long.BYTES), Conversions.longSizeFromByteSize(size));
85      }
86  
87      @ParameterizedTest
88      @ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, Integer.MAX_VALUE})
89      void testIntSizeFromLongSize(int size) {
90          Assertions.assertEquals((int) Math.min(size * 2L, Integer.MAX_VALUE), Conversions.intSizeFromLongSize(size));
91      }
92  
93      @ParameterizedTest
94      @ValueSource(ints = {0, 1, 2, 3, 4, 5, Integer.MAX_VALUE})
95      void testLongSizeFromIntSize(int size) {
96          Assertions.assertEquals((int) Math.ceil((double) size / 2), Conversions.longSizeFromIntSize(size));
97      }
98  
99      @RepeatedTest(value = 5)
100     void testInt2Long() {
101         final int v = ThreadLocalRandom.current().nextInt();
102         Assertions.assertEquals(new SplitMix64(v).nextLong(), Conversions.int2Long(v));
103     }
104 
105     @RepeatedTest(value = 5)
106     void testInt2IntArray() {
107         final int v = ThreadLocalRandom.current().nextInt();
108         getIntLengths().forEach(len -> {
109             Assertions.assertArrayEquals(Conversions.long2IntArray(v, len),
110                                          Conversions.int2IntArray(v, len));
111         });
112     }
113 
114     @RepeatedTest(value = 5)
115     void testInt2LongArray() {
116         final int v = ThreadLocalRandom.current().nextInt();
117         getIntLengths().forEach(len -> {
118             final long[] a = Conversions.int2LongArray(v, len);
119             Assertions.assertArrayEquals(Conversions.long2LongArray(v, len), a);
120             if (len != 0) {
121                 // Special case of expansion to length 1
122                 // Expandion is done by mixing
123                 Assertions.assertEquals(Conversions.int2Long(v), a[0]);
124             }
125         });
126     }
127 
128     @RepeatedTest(value = 5)
129     void testLong2Int() {
130         final long v = ThreadLocalRandom.current().nextLong();
131         Assertions.assertEquals(NumberFactory.makeInt(v), Conversions.long2Int(v));
132     }
133 
134     @RepeatedTest(value = 5)
135     void testLong2IntArray() {
136         // Avoid seed == 0 - 0x9e3779b97f4a7c15L. See testLong2IntArrayLength2NotAllZero.
137         long seed;
138         do {
139             seed = ThreadLocalRandom.current().nextLong();
140         } while (seed == -GOLDEN_RATIO);
141         final long v = seed;
142         getIntLengths().forEach(len -> {
143             final int longs = Conversions.longSizeFromIntSize(len);
144             // Little-endian conversion
145             final ByteBuffer bb = ByteBuffer.allocate(longs * Long.BYTES).order(ByteOrder.LITTLE_ENDIAN);
146             LongStream.generate(new SplitMix64(v)::nextLong).limit(longs).forEach(bb::putLong);
147             bb.clear();
148             final int[] expected = new int[len];
149             for (int i = 0; i < len; i++) {
150                 expected[i] = bb.getInt();
151             }
152             Assertions.assertArrayEquals(expected,
153                 Conversions.long2IntArray(v, len));
154 
155             // Note:
156             // long -> int[] position[0] != long -> int
157             // Reduction is done by folding upper and lower using xor
158         });
159     }
160 
161     /**
162      * Test the long2IntArray conversion avoids an input that will generate a zero from the
163      * SplitMix64-style RNG. This prevents creating an array of length 2 that is zero.
164      *
165      * <p>This special case avoids creating a small state Xor-based generator such as
166      * XoRoShiRo64StarStar with a seed of all zeros.
167      */
168     @Test
169     void testLong2IntArrayLength2NotAllZero() {
170         // The first output from the SplitMix64 is mix(seed + GOLDEN_RATIO).
171         // Create the seed to ensure a zero output from the mix function.
172         final long seed = -GOLDEN_RATIO;
173         Assertions.assertEquals(0, new SplitMix64(seed).nextLong());
174 
175         // Note: This cannot occur for int2IntArray as the SplitMix64 is seeded with the int.
176         // This ignores the case of an output int[] of length 1 which could be zero.
177         // An int -> int[1] conversion is nonsensical and should not be performed by the library.
178         Assertions.assertNotEquals(0, new SplitMix64((int) seed).nextLong());
179 
180         // The conversion should detect this case and a zero seed of length 2 should not happen.
181         final int[] actual = Conversions.long2IntArray(seed, 2);
182         Assertions.assertFalse(Arrays.equals(new int[2], actual));
183 
184         // Longer arrays may be a partially zero as the generator state passes through
185         // the zero-point.
186         Assertions.assertArrayEquals(
187             Arrays.copyOf(Conversions.long2IntArray(seed - GOLDEN_RATIO, 2), 4),
188             Conversions.long2IntArray(seed - GOLDEN_RATIO, 4));
189     }
190 
191     @RepeatedTest(value = 5)
192     void testLong2LongArray() {
193         final long v = ThreadLocalRandom.current().nextLong();
194         getIntLengths().forEach(len -> {
195             Assertions.assertArrayEquals(LongStream.generate(new SplitMix64(v)::nextLong).limit(len).toArray(),
196                 Conversions.long2LongArray(v, len));
197         });
198     }
199 
200     @ParameterizedTest
201     @MethodSource(value = {"getIntLengths"})
202     void testIntArray2Int(int ints) {
203         final int[] seed = ThreadLocalRandom.current().ints(ints).toArray();
204         // xor all the bytes
205         int expected = 0;
206         for (final int i : seed) {
207             expected ^= i;
208         }
209         Assertions.assertEquals(expected, Conversions.intArray2Int(seed));
210     }
211 
212     @ParameterizedTest
213     @MethodSource(value = {"getIntLengths"})
214     void testIntArray2Long(int ints) {
215         final int[] seed = ThreadLocalRandom.current().ints(ints).toArray();
216 
217         // int[] -> long[] -> long
218         // Concatenate all ints in little-endian order to bytes
219         final int outLength = Conversions.longSizeFromIntSize(ints);
220         final int[] filledSeed = Arrays.copyOf(seed, outLength * 2);
221         final ByteBuffer bb = ByteBuffer.allocate(filledSeed.length * Integer.BYTES)
222                 .order(ByteOrder.LITTLE_ENDIAN);
223         Arrays.stream(filledSeed).forEach(bb::putInt);
224         // xor all the bytes read as longs
225         long expected = 0;
226         bb.flip();
227         for (int i = outLength; i-- != 0;) {
228             final long l = bb.getLong();
229             expected ^= l;
230         }
231 
232         Assertions.assertEquals(expected, Conversions.intArray2Long(seed));
233     }
234 
235     @ParameterizedTest
236     @MethodSource(value = {"getIntLengths"})
237     void testIntArray2LongComposed(int ints) {
238         final int[] seed = ThreadLocalRandom.current().ints(ints).toArray();
239         final long expected = new LongArray2Long().convert(new IntArray2LongArray().convert(seed));
240         Assertions.assertEquals(expected, Conversions.intArray2Long(seed));
241     }
242 
243     @ParameterizedTest
244     @MethodSource(value = {"getIntLengths"})
245     void testIntArray2LongArray(int ints) {
246         final int[] seed = ThreadLocalRandom.current().ints(ints).toArray();
247 
248         // Concatenate all bytes in little-endian order to bytes
249         final int outLength = Conversions.longSizeFromIntSize(ints);
250         final ByteBuffer bb = ByteBuffer.allocate(outLength * Long.BYTES)
251                 .order(ByteOrder.LITTLE_ENDIAN);
252         Arrays.stream(seed).forEach(bb::putInt);
253         bb.clear();
254         final long[] expected = new long[outLength];
255         for (int i = 0; i < outLength; i++) {
256             expected[i] = bb.getLong();
257         }
258 
259         Assertions.assertArrayEquals(expected, Conversions.intArray2LongArray(seed, outLength));
260         // Zero fill
261         Assertions.assertArrayEquals(Arrays.copyOf(expected, outLength * 2),
262             Conversions.intArray2LongArray(seed, outLength * 2));
263         // Truncation
264         for (int i = 0; i < outLength; i++) {
265             Assertions.assertArrayEquals(Arrays.copyOf(expected, i), Conversions.intArray2LongArray(seed, i));
266         }
267     }
268 
269     @ParameterizedTest
270     @MethodSource(value = {"getLongLengths"})
271     void testLongArray2Int(long longs) {
272         final long[] seed = ThreadLocalRandom.current().longs(longs).toArray();
273         // xor all the bytes
274         long expected = 0;
275         for (final long i : seed) {
276             expected ^= i;
277         }
278         Assertions.assertEquals((int) (expected ^ expected >>> 32), Conversions.longArray2Int(seed));
279     }
280 
281     @ParameterizedTest
282     @MethodSource(value = {"getLongLengths"})
283     void testLongArray2Long(long longs) {
284         final long[] seed = ThreadLocalRandom.current().longs(longs).toArray();
285         // xor all the bytes
286         long expected = 0;
287         for (final long i : seed) {
288             expected ^= i;
289         }
290         Assertions.assertEquals(expected, Conversions.longArray2Long(seed));
291     }
292 
293     @ParameterizedTest
294     @MethodSource(value = {"getLongLengths"})
295     void testLongArray2IntArray(int longs) {
296         final long[] seed = ThreadLocalRandom.current().longs(longs).toArray();
297 
298         // Concatenate all bytes in little-endian order to bytes
299         final int outLength = Conversions.intSizeFromLongSize(longs);
300         final ByteBuffer bb = ByteBuffer.allocate(longs * Long.BYTES)
301                 .order(ByteOrder.LITTLE_ENDIAN);
302         Arrays.stream(seed).forEach(bb::putLong);
303         bb.clear();
304         final int[] expected = new int[outLength];
305         for (int i = 0; i < outLength; i++) {
306             expected[i] = bb.getInt();
307         }
308 
309         Assertions.assertArrayEquals(expected, Conversions.longArray2IntArray(seed, outLength));
310         // Zero fill
311         Assertions.assertArrayEquals(Arrays.copyOf(expected, outLength * 2),
312             Conversions.longArray2IntArray(seed, outLength * 2));
313         // Truncation
314         for (int i = 0; i < outLength; i++) {
315             Assertions.assertArrayEquals(Arrays.copyOf(expected, i), Conversions.longArray2IntArray(seed, i));
316         }
317     }
318 
319     @ParameterizedTest
320     @MethodSource(value = {"getByteLengths"})
321     void testByteArray2Int(int bytes) {
322         final byte[] seed = new byte[bytes];
323         ThreadLocalRandom.current().nextBytes(seed);
324 
325         // byte[] -> int[] -> int
326         // Concatenate all bytes in little-endian order to bytes
327         final int outLength = Conversions.intSizeFromByteSize(bytes);
328         final byte[] filledSeed = Arrays.copyOf(seed, outLength * Integer.BYTES);
329         final ByteBuffer bb = ByteBuffer.wrap(filledSeed)
330                 .order(ByteOrder.LITTLE_ENDIAN);
331         // xor all the bytes read as ints
332         int expected = 0;
333         for (int i = outLength; i-- != 0;) {
334             final long l = bb.getInt();
335             expected ^= l;
336         }
337 
338         Assertions.assertEquals(expected, Conversions.byteArray2Int(seed));
339     }
340 
341     @ParameterizedTest
342     @MethodSource(value = {"getByteLengths"})
343     void testByteArray2IntComposed(int bytes) {
344         final byte[] seed = new byte[bytes];
345         ThreadLocalRandom.current().nextBytes(seed);
346         final int expected = new IntArray2Int().convert(new ByteArray2IntArray().convert(seed));
347         Assertions.assertEquals(expected, Conversions.byteArray2Int(seed));
348     }
349 
350     @ParameterizedTest
351     @MethodSource(value = {"getByteLengths"})
352     void testByteArray2IntArray(int bytes) {
353         final byte[] seed = new byte[bytes];
354         ThreadLocalRandom.current().nextBytes(seed);
355 
356         // Concatenate all bytes in little-endian order to bytes
357         final int outLength = Conversions.intSizeFromByteSize(bytes);
358         final byte[] filledSeed = Arrays.copyOf(seed, outLength * Integer.BYTES);
359         final ByteBuffer bb = ByteBuffer.wrap(filledSeed)
360                 .order(ByteOrder.LITTLE_ENDIAN);
361         final int[] expected = new int[outLength];
362         for (int i = 0; i < outLength; i++) {
363             expected[i] = bb.getInt();
364         }
365 
366         Assertions.assertArrayEquals(expected, Conversions.byteArray2IntArray(seed, outLength));
367         // Zero fill
368         Assertions.assertArrayEquals(Arrays.copyOf(expected, outLength * 2),
369             Conversions.byteArray2IntArray(seed, outLength * 2));
370         // Truncation
371         for (int i = 0; i < outLength; i++) {
372             Assertions.assertArrayEquals(Arrays.copyOf(expected, i), Conversions.byteArray2IntArray(seed, i));
373         }
374     }
375 
376     @ParameterizedTest
377     @MethodSource(value = {"getByteLengths"})
378     void testByteArray2Long(int bytes) {
379         final byte[] seed = new byte[bytes];
380         ThreadLocalRandom.current().nextBytes(seed);
381 
382         // byte[] -> long[] -> long
383         // Concatenate all bytes in little-endian order to bytes
384         final int outLength = Conversions.longSizeFromByteSize(bytes);
385         final byte[] filledSeed = Arrays.copyOf(seed, outLength * Long.BYTES);
386         final ByteBuffer bb = ByteBuffer.wrap(filledSeed)
387                 .order(ByteOrder.LITTLE_ENDIAN);
388         // xor all the bytes read as longs
389         long expected = 0;
390         for (int i = outLength; i-- != 0;) {
391             final long l = bb.getLong();
392             expected ^= l;
393         }
394 
395         Assertions.assertEquals(expected, Conversions.byteArray2Long(seed));
396     }
397 
398     @ParameterizedTest
399     @MethodSource(value = {"getByteLengths"})
400     void testByteArray2LongComposed(int bytes) {
401         final byte[] seed = new byte[bytes];
402         ThreadLocalRandom.current().nextBytes(seed);
403         final long expected = new LongArray2Long().convert(new ByteArray2LongArray().convert(seed));
404         Assertions.assertEquals(expected, Conversions.byteArray2Long(seed));
405     }
406 
407     @ParameterizedTest
408     @MethodSource(value = {"getByteLengths"})
409     void testByteArray2LongArray(int bytes) {
410         final byte[] seed = new byte[bytes];
411         ThreadLocalRandom.current().nextBytes(seed);
412 
413         // Concatenate all bytes in little-endian order to bytes
414         final int outLength = Conversions.longSizeFromByteSize(bytes);
415         final byte[] filledSeed = Arrays.copyOf(seed, outLength * Long.BYTES);
416         final ByteBuffer bb = ByteBuffer.wrap(filledSeed)
417                 .order(ByteOrder.LITTLE_ENDIAN);
418         final long[] expected = new long[outLength];
419         for (int i = 0; i < outLength; i++) {
420             expected[i] = bb.getLong();
421         }
422 
423         Assertions.assertArrayEquals(expected, Conversions.byteArray2LongArray(seed, outLength));
424         // Zero fill
425         Assertions.assertArrayEquals(Arrays.copyOf(expected, outLength * 2),
426             Conversions.byteArray2LongArray(seed, outLength * 2));
427         // Truncation
428         for (int i = 0; i < outLength; i++) {
429             Assertions.assertArrayEquals(Arrays.copyOf(expected, i), Conversions.byteArray2LongArray(seed, i));
430         }
431     }
432 }