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.SplittableRandom; 20 import org.junit.jupiter.api.Assertions; 21 import org.junit.jupiter.api.Test; 22 import org.junit.jupiter.params.ParameterizedTest; 23 import org.junit.jupiter.params.provider.CsvSource; 24 25 /** 26 * Tests for {@link LXMSupport}. 27 * 28 * <p>Note: The LXM generators require the LCG component to be advanced in a single 29 * jump operation by half the period 2<sup>k/2</sup>, where {@code k} is the LCG 30 * state size. This is performed in a single step using coefficients {@code m'} and 31 * {@code c'}. 32 * 33 * <p>This class contains a generic algorithm to compute an arbitrary 34 * LCG update for any power of 2. This can be bootstrap tested using small powers of 35 * 2 by manual LCG iteration. Small powers can then be used to verify increasingly 36 * large powers. The main {@link LXMSupport} class contains precomputed coefficients 37 * {@code m'} and a precursor to {@code c'} for a jump of 2<sup>k/2</sup> 38 * assuming the multiplier {@code m} used in the LXM generators. The coefficient 39 * {@code c'} is computed by multiplying by the generator additive constant. The output 40 * coefficients have the following properties: 41 * <ul> 42 * <li>The multiplier {@code m'} is constant. 43 * <li>The lower half of the multiplier {@code m'} is 1. 44 * <li>The lower half of the additive parameter {@code c'} is 0. 45 * <li>The upper half of the additive parameter {@code c'} is odd when {@code c} is odd. 46 * </ul> 47 */ 48 class LXMSupportTest { 49 /** A mask to clear the lower 5 bits of an integer. */ 50 private static final int CLEAR_LOWER_5 = -1 << 5; 51 52 @Test 53 void testLea32() { 54 // Code generated using the reference c code provided by Guy Steele. 55 // Note: A java implementation requires using the unsigned shift '>>>' operator. 56 // 57 // uint32_t lea32(uint32_t z) { 58 // z = (z ^ (z >> 16)); 59 // z *= 0xd36d884b; 60 // z = (z ^ (z >> 16)); 61 // z *= 0xd36d884b; 62 // return z ^ (z >> 16); 63 // } 64 final int[] expected = { 65 0x4fe04eac, 0x7bc5cb6c, 0x29af7e05, 0xf80de147, 66 0xb90bc13a, 0x6fbce371, 0x3dbbfab0, 0xcf366cd9, 67 0x90c9c2a2, 0x988ea20f, 0x75fc2207, 0x58197217, 68 0xdbc584a5, 0x5d232d06, 0xd4faec13, 0xfa1fb8fd, 69 0xea45a9d4, 0xb2bdb43c, 0x0502b325, 0x2ebca83c, 70 0x3337cf53, 0x5531f920, 0x3edf02c5, 0xa9b79cf7, 71 0x80ff2c21, 0xaba7498f, 0xa7dd9739, 0xb85c77ea, 72 0x4e846134, 0x99461d4e, 0x87c027b1, 0xc8c37ec7, 73 0x457301bc, 0xa8d33a18, 0x87420fcc, 0x1c5a5b6d, 74 0x44b0e3ff, 0x45459875, 0x99d6ef70, 0x12e06019, 75 }; 76 int state = 0x012de1ba; 77 final int increment = 0xc8161b42; 78 79 for (final int e : expected) { 80 Assertions.assertEquals(e, LXMSupport.lea32(state += increment)); 81 } 82 } 83 84 @ParameterizedTest 85 @CsvSource({ 86 "235642368, 72987979, 792134597", 87 // LXM 32-bit multiplier: 88 // -1380669139 == 0xadb4a92d 89 "-1380669139, 617328132, 236746283", 90 "-1380669139, -789374989, -180293891", 91 "-1380669139, 67236828, 236784628", 92 "-1380669139, -13421542, 42", 93 }) 94 void testLcgAdvancePow2(int m, int c, int state) { 95 // Bootstrap the first powers 96 int s = state; 97 for (int i = 0; i < 1; i++) { 98 s = m * s + c; 99 } 100 Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 0), "2^0 cycles"); 101 for (int i = 0; i < 1; i++) { 102 s = m * s + c; 103 } 104 Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 1), "2^1 cycles"); 105 for (int i = 0; i < 2; i++) { 106 s = m * s + c; 107 } 108 Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 2), "2^2 cycles"); 109 for (int i = 0; i < 4; i++) { 110 s = m * s + c; 111 } 112 Assertions.assertEquals(s, lcgAdvancePow2(state, m, c, 3), "2^3 cycles"); 113 114 // Larger powers should align 115 for (int n = 3; n < 31; n++) { 116 final int n1 = n + 1; 117 Assertions.assertEquals( 118 lcgAdvancePow2(lcgAdvancePow2(state, m, c, n), m, c, n), 119 lcgAdvancePow2(state, m, c, n1), () -> "2^" + n1 + " cycles"); 120 } 121 122 // Larger/negative powers are ignored 123 for (final int i : new int[] {32, 67868, Integer.MAX_VALUE, Integer.MIN_VALUE, -26762, -2, -1}) { 124 final int n = i; 125 Assertions.assertEquals(state, lcgAdvancePow2(state, m, c, n), 126 () -> "2^" + n + " cycles"); 127 } 128 } 129 130 @Test 131 void testLcg32Advance2Pow16Constants() { 132 // Computing with a addition of 1 will compute: 133 // m^(2^16) 134 // product {m^(2^i) + 1} for i in [0, 15] 135 final int[] out = new int[2]; 136 lcgAdvancePow2(LXMSupport.M32, 1, 16, out); 137 Assertions.assertEquals(LXMSupport.M32P, out[0], "m'"); 138 Assertions.assertEquals(LXMSupport.C32P, out[1], "c'"); 139 // Check the special values of the low half 140 Assertions.assertEquals(1, out[0] & 0xffff, "low m'"); 141 Assertions.assertEquals(0, out[1] & 0xffff, "low c'"); 142 } 143 144 /** 145 * Test the precomputed 32-bit LCG advance constant in {@link LXMSupport} can be used 146 * to compute the same jump as the bootstrap tested generic version in this class. 147 */ 148 @Test 149 void testLcgAdvance2Pow16() { 150 final SplittableRandom r = new SplittableRandom(); 151 final int[] out = new int[2]; 152 153 for (int i = 0; i < 2000; i++) { 154 // Must be odd 155 final int c = r.nextInt() | 1; 156 lcgAdvancePow2(LXMSupport.M32, c, 16, out); 157 final int a = out[1]; 158 // Test assumptions 159 Assertions.assertEquals(1, (a >>> 16) & 0x1, "High half c' should be odd"); 160 Assertions.assertEquals(0, a & 0xffff, "Low half c' should be 0"); 161 // This value can be computed from the constant 162 Assertions.assertEquals(a, LXMSupport.C32P * c); 163 } 164 } 165 166 /** 167 * Compute the multiplier {@code m'} and addition {@code c'} to advance the state of a 168 * 32-bit Linear Congruential Generator (LCG) a number of consecutive steps: 169 * 170 * <pre> 171 * s = m' * s + c' 172 * </pre> 173 * 174 * <p>A number of consecutive steps can be computed in a single multiply and add 175 * operation. This method computes the accumulated multiplier and addition for the 176 * given number of steps expressed as a power of 2. Provides support to advance for 177 * 2<sup>k</sup> for {@code k in [0, 31)}. Any power {@code >= 32} is ignored as this 178 * would wrap the generator to the same point. Negative powers are ignored but do not 179 * throw an exception. 180 * 181 * <p>Based on the algorithm from: 182 * 183 * <blockquote>Brown, F.B. (1994) Random number generation with arbitrary strides, 184 * Transactions of the American Nuclear Society 71.</blockquote> 185 * 186 * @param m Multiplier 187 * @param c Constant 188 * @param k Number of advance steps as a power of 2 (range [0, 31]) 189 * @param out Output result [m', c'] 190 * @see <A 191 * href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides"> 192 * Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of 193 * the American Nuclear Society 71</a> 194 */ 195 private static void lcgAdvancePow2(int m, int c, int k, int[] out) { 196 // If any bits above the first 5 are set then this would wrap the generator to 197 // the same point as multiples of period (2^32). 198 // It also identifies negative powers to ignore. 199 if ((k & CLEAR_LOWER_5) != 0) { 200 // m'=1, c'=0 201 out[0] = 1; 202 out[1] = 0; 203 return; 204 } 205 206 int mp = m; 207 int a = c; 208 209 for (int i = k; i != 0; i--) { 210 // Update the multiplier and constant for the next power of 2 211 a = (mp + 1) * a; 212 mp *= mp; 213 } 214 out[0] = mp; 215 out[1] = a; 216 } 217 218 /** 219 * Compute the advanced state of a 32-bit Linear Congruential Generator (LCG). The 220 * base generator advance step is: 221 * 222 * <pre> 223 * s = m * s + c 224 * </pre> 225 * 226 * <p>A number of consecutive steps can be computed in a single multiply and add 227 * operation. This method computes the update coefficients and applies them to the 228 * given state. 229 * 230 * <p>This method is used for testing only. For arbitrary jumps an efficient implementation 231 * would inline the computation of the update coefficients; or for repeat jumps of the same 232 * size pre-compute the coefficients once. 233 * 234 * <p>This is package-private for use in {@link AbstractLXMTest} to provide jump functionality 235 * to a composite LXM generator. 236 * 237 * @param s State 238 * @param m Multiplier 239 * @param c Constant 240 * @param k Number of advance steps as a power of 2 (range [0, 31]) 241 * @return the new state 242 * @see <A 243 * href="https://www.osti.gov/biblio/89100-random-number-generation-arbitrary-strides"> 244 * Brown, F.B. (1994) Random number generation with arbitrary strides, Transactions of 245 * the American Nuclear Society 71</a> 246 */ 247 static int lcgAdvancePow2(int s, int m, int c, int k) { 248 final int[] out = new int[2]; 249 lcgAdvancePow2(m, c, k, out); 250 final int mp = out[0]; 251 final int ap = out[1]; 252 return mp * s + ap; 253 } 254 }