1 /* 2 * License : The MIT License 3 * Copyright(c) 2021 Olyutorskii 4 */ 5 6 package io.github.olyutorskii.aletojio.rng.fibo; 7 8 import io.github.olyutorskii.aletojio.rng.RndInt32; 9 import java.util.Objects; 10 11 /** 12 * Fibonacci Linear-feedback shift register (LFSR) Pseudo Random Generator. 13 * 14 * <p>A 32-bit length shift register is provided. 15 * Any number of taps can be specified. 16 * 17 * <p>The shift operation is performed 32 times to obtain a 32-bit random integer from MSB to LSB. 18 * 19 * <p>For LFSR to have a period of maximum length, 20 * at least the conditions 21 * "number of taps is even" and "Set of taps is setwise co-prime" 22 * must be satisfied. 23 * (but not sufficient) 24 * 25 * <p>If polynomial: {@code [x**(tapN) + x**(tapN-1) + ... + x**(tap1) + 1]} is primitive over GF(2), 26 * LFSR has maximum long period. 27 * 28 * @see <a href="https://en.wikipedia.org/wiki/Linear-feedback_shift_register"> 29 * Linear-feedback shift register (Wikipedia) 30 * </a> 31 */ 32 public class LfShiftReg implements RndInt32 { 33 34 /** Default seed value. */ 35 public static final int DEF_SEED = 0b1; 36 37 private static final String MSG_ILLSEED = "Seed must be non-zero"; 38 private static final String MSG_ILLTAPPOS = "Illegal tap position"; 39 40 41 private final int tapMask; 42 43 private int seed; 44 45 46 /** 47 * Constructor. 48 * 49 * <p>Tap position numbers starts from 1 to 32. 50 * Value (1) points LSB of LFSR register. 51 * Value (32) points MSB of LFSR register. 52 * 53 * <p>For LFSR to have a period of maximum length, 54 * at least the conditions 55 * "number of taps is even" and "Set of taps is setwise co-prime" 56 * must be satisfied. 57 * (but not sufficient) 58 * 59 * <p>If polynomial: {@code [x**(tapN) + x**(tapN-1) + ... + x**(tap1) + 1]} 60 * is primitive over GF(2), 61 * LFSR has maximum long period. 62 * 63 * <P>Taps example : (32, 31, 30, 10), (32, 31, 29, 1), (32, 25, 17, 7), etc. 64 * 65 * @param taps taps in register 66 * @throws NullPointerException taps is null. 67 * @throws IllegalArgumentException illegal tap position 68 * @see <a href="https://en.wikipedia.org/wiki/Linear-feedback_shift_register"> 69 * Linear-feedback shift register (Wikipedia) 70 * </a> 71 */ 72 public LfShiftReg(int... taps) 73 throws NullPointerException, IllegalArgumentException { 74 super(); 75 this.tapMask = taps2Mask(taps); 76 this.seed = DEF_SEED; 77 return; 78 } 79 80 /** 81 * Constructor. 82 * 83 * <p>Default taps are <code> { 32, 31, 30, 10 } </code> 84 */ 85 public LfShiftReg() { 86 this(32, 31, 30, 10); 87 return; 88 } 89 90 91 /** 92 * Convert from LFSR-taps notation to bitMask. 93 * 94 * <p>Tap position numbers starts from 1 to 32. 95 * Value (1) points LSB of LFSR register. 96 * Value (32) points MSB of LFSR register. 97 * 98 * @param taps Tap numbers array. Order is ignored. Duplications are ignored. 99 * @return Bit mask in which tapped bits are set. 100 * @throws IllegalArgumentException illegal tap position 101 * @throws NullPointerException taps is null. 102 */ 103 protected static int taps2Mask(int... taps) 104 throws IllegalArgumentException, NullPointerException { 105 Objects.requireNonNull(taps); 106 107 int result = 0b0; 108 109 for (int tap : taps) { 110 if (tap < 1 || Integer.SIZE < tap) { 111 throw new IllegalArgumentException(MSG_ILLTAPPOS); 112 } 113 114 int bitPos = tap - 1; 115 int tapBit = 0b1 << bitPos; 116 117 result |= tapBit; 118 } 119 120 return result; 121 } 122 123 /** 124 * XOR whole bit sequence. 125 * 126 * <p>{@code result = b0 xor b1 xor ... xor b31} 127 * 128 * <p>Equivalent to <code>[ Integer.bitCount(i) & 1 ]</code> 129 * or ODD Parity. 130 * 131 * <p>Yes, this is part of linear map function in boolean algebra. 132 * 133 * @param iVal bit pattern 134 * @return XOR result (1 or 0) 135 */ 136 public static int xorBitSeq(int iVal) { 137 int b32 = iVal; 138 139 b32 ^= b32 >>> 16; // 1 << 4 140 b32 ^= b32 >>> 8; // 1 << 3 141 b32 ^= b32 >>> 4; // 1 << 2 142 b32 ^= b32 >>> 2; // 1 << 1 143 b32 ^= b32 >>> 1; // 1 << 0 144 145 int result = b32 & 0b1; 146 return result; 147 } 148 149 150 /** 151 * {@inheritDoc} 152 * 153 * <p>The 32bits obtained by repeating the register-shift 32times are returned, 154 * packed sequentially starting from MSB. 155 * 156 * @return {@inheritDoc} 157 */ 158 @Override 159 public int nextInt32() { 160 int newSeed = this.seed; 161 162 for (int ct = 0; ct < Integer.SIZE; ct++) { 163 int tappedSeed = newSeed & this.tapMask; 164 int xoredBit = xorBitSeq(tappedSeed); // 0 or 1 165 newSeed = (newSeed << 1) | xoredBit; 166 } 167 168 this.seed = newSeed; 169 int result = newSeed; 170 171 return result; 172 } 173 174 175 /** 176 * Set seed. 177 * 178 * <p>Seed value must not be 0. 179 * 180 * <p>It will take some time before the extreme bias in seed value pop-counts is corrected. 181 * 182 * @param newSeed seed value 183 * @throws IllegalArgumentException Seed must be non-zero 184 */ 185 public void setSeed(int newSeed) throws IllegalArgumentException { 186 if (newSeed == 0b0) { 187 throw new IllegalArgumentException(MSG_ILLSEED); 188 } 189 this.seed = newSeed; 190 return; 191 } 192 193 /** 194 * {@inheritDoc} 195 * 196 * @return {@inheritDoc} 197 */ 198 @Override 199 public String toString() { 200 StringBuilder sb = new StringBuilder(32 + 8 - 1); 201 202 for (int ct = 0; ct < Integer.SIZE; ct++) { 203 if (ct % 4 == 0 && sb.length() != 0) { 204 sb.append('-'); 205 } 206 207 int bSft = Integer.SIZE - ct - 1; 208 int bit = (this.seed >>> bSft) & 0b1; 209 int tap = (this.tapMask >>> bSft) & 0b1; 210 211 char ch; 212 if (bit == 0) { 213 if (tap == 0) { 214 ch = 'o'; 215 } else { 216 ch = 'O'; 217 } 218 } else { 219 if (tap == 0) { 220 ch = 'i'; 221 } else { 222 ch = 'I'; 223 } 224 } 225 226 sb.append(ch); 227 } 228 229 String result = sb.toString(); 230 return result; 231 } 232 233 }