View Javadoc
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) &amp; 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 }