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   * Lagged Fibonacci Pseudo Random Generator(LFG).
13   *
14   * <p>Recurrence relation sequences : {@code X(n) = X(n-j) + X(n-k)}
15   *
16   * <p>If generation(1,2) given, it generates the so-called Fibonacchi numbers sequence.
17   *
18   * <p>Typical generation pairs.
19   * <ul>
20   * <li>(1,2)
21   * <li>(7,10)
22   * <li>(24,55)
23   * <li>(353,521)
24   * <li>(861,1279)
25   * <li>(9739,23209)
26   * </ul>
27   *
28   * <p>Seed values are mixed well if the number of generations(k,j) are prime to each other.
29   *
30   * <p>If polynomial: {@code y = x**k + x**j + 1} is primitive over GF(2),
31   * generator has more long period.
32   *
33   * @see <a href="https://en.wikipedia.org/wiki/Lagged_Fibonacci_generator">
34   * Lagged_Fibonacci_generator (Wikipedia)
35   * </a>
36   */
37  public class LagFib implements RndInt32 {
38  
39      private static final int INITIAL_ODD = 0x01;
40  
41      private static final String ERRMSG_POSARG  = "Generation must be positive";
42      private static final String ERRMSG_ORDER   = "Older generation must be greater than younger";
43      private static final String ERRMSG_ODDSEED = "At least one of the seeds must be odd number";
44      private static final String ERRMSG_SEEDLEN = "Unmatch seeds length";
45  
46  
47      private final int[] recRing;
48      private final int recSize;
49  
50      private int recentIdx;
51  
52      private final int genYng;
53      private final int genOld;
54  
55  
56      /**
57       * Constructor.
58       *
59       * <p>Number of generation must be greater than 0.
60       *
61       * <p>Older generation number must be greater than younger.
62       *
63       * @param genYounger younger past generation X(n-j)
64       * @param genOlder older past generation X(n-k)
65       * @throws IndexOutOfBoundsException Given generation is out of range
66       * @throws IllegalArgumentException older generation is not greater than youger generation
67       */
68      public LagFib(int genYounger, int genOlder)
69              throws IndexOutOfBoundsException, IllegalArgumentException {
70          super();
71  
72          if (genYounger < 1) {
73              throw new IndexOutOfBoundsException(ERRMSG_POSARG);
74          }
75          if (genYounger >= genOlder) {
76              throw new IllegalArgumentException(ERRMSG_ORDER);
77          }
78  
79          this.genYng = genYounger;
80          this.genOld = genOlder;
81  
82          this.recRing = new int[this.genOld];
83          this.recSize = this.recRing.length;
84  
85          this.recentIdx = 0;
86  
87          int lastIdx = this.recSize - 1;
88          this.recRing[lastIdx] = INITIAL_ODD;
89  
90          assert hasOdd(this.recRing);
91  
92          return;
93      }
94  
95  
96      /**
97       * Check whether int array contains odd number or not.
98       *
99       * @param iVec int array
100      * @return true if odd number exists.
101      * @throws NullPointerException argument is null
102      */
103     public static boolean hasOdd(int... iVec) throws NullPointerException {
104         Objects.requireNonNull(iVec);
105 
106         for (int iSeed : iVec) {
107             boolean isOdd = (iSeed & 0b1) != 0;
108             if (isOdd) {
109                 return true;
110             }
111         }
112 
113         return false;
114     }
115 
116 
117     /**
118      * Check seeds.
119      *
120      * @param initVec seeds
121      * @throws IllegalArgumentException illegal seeds
122      */
123     protected void checkSeeds(int... initVec) throws IllegalArgumentException {
124         if (!hasOdd(initVec)) {
125             throw new IllegalArgumentException(ERRMSG_ODDSEED);
126         }
127         return;
128     }
129 
130     /**
131      * Set seed.
132      *
133      * <p>At least one of the seeds must be odd number.
134      *
135      * <p>Seeds must be old-generation length array.
136      *
137      * @param initVec seed array
138      * @throws NullPointerException argument is null
139      * @throws IndexOutOfBoundsException unmatch seeds length
140      * @throws IllegalArgumentException odd number is not be found
141      */
142     public void setSeed(int... initVec)
143             throws NullPointerException, IndexOutOfBoundsException, IllegalArgumentException {
144         Objects.requireNonNull(initVec);
145 
146         if (initVec.length != this.recSize) {
147             throw new IndexOutOfBoundsException(ERRMSG_SEEDLEN);
148         }
149 
150         checkSeeds(initVec);
151 
152         for (int ct = 0; ct < initVec.length; ct++) {
153             int idx = this.recentIdx + ct;
154             if (idx >= this.recSize) {
155                 idx -= this.recSize;
156             }
157             this.recRing[idx] = initVec[ct];
158         }
159 
160         return;
161     }
162 
163     /**
164      * Return generational past value.
165      *
166      * @param genNo generation number
167      * @return past value
168      */
169     private int genValue(int genNo) {
170         assert 1 <= genNo;
171         assert genNo <= this.recSize;
172 
173         int rawIdx = this.recentIdx + genNo - 1;
174         if (rawIdx >= this.recSize) {
175             rawIdx -= this.recSize;
176         }
177 
178         int result = this.recRing[rawIdx];
179         return result;
180     }
181 
182     /**
183      * Binary operation between 2 taps.
184      *
185      * <p>Just adding.
186      *
187      * @param tap1 tap 1st
188      * @param tap2 tap 2nd
189      * @return result
190      */
191     protected int binOp(int tap1, int tap2) {
192         int result = tap1 + tap2;
193         return result;
194     }
195 
196     /**
197      * {@inheritDoc}
198      *
199      * @return {@inheritDoc}
200      */
201     @Override
202     public int nextInt32() {
203         int valYng = genValue(this.genYng);
204         int valOld = genValue(this.genOld);
205 
206         int result = binOp(valYng, valOld);
207 
208         this.recentIdx--;
209         if (this.recentIdx < 0) {
210             int lastIdx = this.recSize - 1;
211             this.recentIdx = lastIdx;
212         }
213         this.recRing[this.recentIdx] = result;
214 
215         return result;
216     }
217 
218 }