View Javadoc
1   /*
2    * License : The MIT License
3    * Copyright(c) 2022 olyutorskii
4    */
5   
6   package io.github.olyutorskii.aletojio.rng.lcg;
7   
8   /**
9    * Common implementation of Linear congruential generator(LCG).
10   *
11   * <p>LCG is a commonly used random number generator in the past.
12   *
13   * <p>Recurrence relation sequences : {@code X(n+1) = (X(n) * Mul + Inc) mod Mod}
14   *
15   * <ul>
16   * <li>Mul : Multiplier
17   * <li>Inc : Increment
18   * <li>Mod : Modulus
19   * </ul>
20   */
21  public abstract class AbstractLcg {
22  
23      /** 31bit bitmask. */
24      protected static final long MASK_B31 = (1L << 31) - 1L;
25      /** 32bit bitmask. */
26      protected static final long MASK_B32 = (1L << 32) - 1L;
27  
28      private static final long DEF_SEED = 0x01L;
29  
30      private static final String ERRMSG_MUL = "multiplier must be 1 or greater";
31      private static final String ERRMSG_INC = "increment must be 0 or greater";
32      private static final String ERRMSG_MOD = "modulus must be 2 or greater";
33      private static final String ERRMSG_SEED = "seed must not be negative";
34  
35  
36      private long seed;
37  
38      private final long multiplier;
39      private final long increment;
40      private final long modulus;
41  
42      // if not 0L, use bitmask operator(&) instead of modulo oeprator(%)
43      private final long modMask;
44  
45  
46      /**
47       * Constructor.
48       *
49       * <ul>
50       * <li>Multiplier must be 1 or greatrer.
51       * <li>Increment must be 0 or greatrer.
52       * <li>Modulus must be 2 or greater.
53       * </ul>
54       *
55       * @param mulArg multiplier
56       * @param incArg increment
57       * @param modArg modulus
58       * @throws IllegalArgumentException illegal argument
59       */
60      protected AbstractLcg(long mulArg, long incArg, long modArg)
61              throws IllegalArgumentException {
62          super();
63  
64          if (mulArg < 1) {
65              throw new IllegalArgumentException(ERRMSG_MUL);
66          }
67  
68          if (incArg < 0) {
69              throw new IllegalArgumentException(ERRMSG_INC);
70          }
71  
72          // modulus must be greater than either multiplier or increment
73          if (modArg < 2) {
74              throw new IllegalArgumentException(ERRMSG_MOD);
75          }
76  
77          this.multiplier = mulArg;
78          this.increment = incArg;
79          this.modulus = modArg;
80  
81          this.modMask = calcModMask(this.modulus);
82  
83          this.seed = DEF_SEED;
84  
85          return;
86      }
87  
88  
89      /**
90       * Calculate modulus mask if modulus parameter is 2**N.
91       *
92       * <ul>
93       * <li>If 0b100, return 0b11.
94       * <li>If 0b101, return 0.
95       * <li>If 0b10000, return 0b1111.
96       * <li>If 0b10101, return 0.
97       * <li>If 1(=2**0), return 0.
98       * <li>If 0, return 0.
99       * </ul>
100      *
101      * @param mod modulus parameter
102      * @return modulus bitmask
103      */
104     static long calcModMask(long mod) {
105         if (mod == 0L) return 0L;
106 
107         long dec = mod - 1L;
108         boolean pow2Mod = (mod & dec) == 0L;
109 
110         long newMask;
111         if (pow2Mod) {
112             newMask = dec;
113         } else {
114             newMask = 0L;
115         }
116 
117         return newMask;
118     }
119 
120 
121     /**
122      * Return next random number as int.
123      *
124      * <p>Negative value returned if (and only) result bits are 32.
125      *
126      * @return random number
127      */
128     protected int nextIntImpl() {
129         this.seed = nextSeed();
130         int result = seedToResult();
131         return result;
132     }
133 
134     /**
135      * Calculate next seed value from current seed value.
136      *
137      * @return next seed value
138      * @throws IllegalStateException seed overflow
139      */
140     protected long nextSeed() throws IllegalStateException {
141         long dividend = this.seed * this.multiplier + this.increment;
142 
143         long nextSeed;
144         if (this.modMask == 0) {
145             if (dividend < 0) {
146                 throw new IllegalStateException();
147             }
148             nextSeed = dividend % this.modulus;
149         } else {
150             nextSeed = dividend & this.modMask;
151         }
152 
153         return nextSeed;
154     }
155 
156     /**
157      * Calculate result number from seed.
158      *
159      * @return result number
160      */
161     protected abstract int seedToResult();
162 
163     /**
164      * Set new seed value.
165      *
166      * @param seedArg new seed value
167      * @throws IllegalArgumentException seed too small
168      */
169     public void setSeed(long seedArg) throws IllegalArgumentException {
170         if (seedArg < 0) {
171             throw new IllegalArgumentException(ERRMSG_SEED);
172         }
173 
174         this.seed = seedArg;
175 
176         return;
177     }
178 
179     /**
180      * Get seed value.
181      *
182      * @return seed value
183      */
184     public long getSeed() {
185         return this.seed;
186     }
187 
188 }