Add Bocfel interpreter
[projects/chimara/chimara.git] / interpreters / bocfel / random.c
1 /*-
2  * Copyright 2010-2012 Chris Spiegel.
3  *
4  * This file is part of Bocfel.
5  *
6  * Bocfel is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version
8  * 2 or 3, as published by the Free Software Foundation.
9  *
10  * Bocfel is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Bocfel.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <limits.h>
23 #include <time.h>
24
25 #include "random.h"
26 #include "process.h"
27 #include "zterp.h"
28
29 /* Mersenne Twister. */
30 static uint32_t mt[624];
31 static uint32_t mt_idx = 0;
32
33 static void zterp_srand(uint32_t s)
34 {
35   mt[0] = s;
36   for(int i = 1; i < 624; i++)
37   {
38     mt[i] = 1812433253UL * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i;
39   }
40   mt_idx = 0;
41 }
42
43 static void mt_gen_num(void)
44 {
45   for(int i = 0; i < 624; i++)
46   {
47     uint32_t y;
48
49     y = mt[i] & 0x80000000UL;
50     y |= (mt[(i + 1) % 624]) & 0x7fffffffUL;
51
52     mt[i] = mt[(i + 397) % 624] ^ (y >> 1);
53
54     if(y % 2 == 1) mt[i] ^= 2567483615UL;
55   }
56 }
57
58 static uint32_t zterp_rand(void)
59 {
60   uint32_t y;
61
62   if(mt_idx == 0) mt_gen_num();
63
64   y = mt[mt_idx];
65   y ^= (y >> 11);
66   y ^= (y << 7) & 2636928640UL;
67   y ^= (y << 15) & 4022730752UL;
68   y ^= (y >> 18);
69
70   mt_idx = (mt_idx + 1) % 624;
71
72   return y;
73 }
74
75 static int rng_interval = 0;
76 static int rng_counter  = 0;
77
78 /* Called with 0, seed the PRNG with either
79  * a) a user-provided seed (via -z) if available, or
80  * b) a seed read from a user-provided file/device (via -Z) if
81  *    available, or
82  * c) a seed derived from a hash of the constituent bytes of the value
83  *    returned by time(NULL)
84  *
85  * Called with a value 0 < S < 1000, generate a string of numbers 1, 2,
86  * 3, ..., S, 1, 2, 3, ... S, ... as recommended in the remarks to ยง2.
87  *
88  * Called with a value >= 1000, use that value as a normal seed.
89  */
90 void seed_random(long value)
91 {
92   if(value == 0)
93   {
94     if(options.random_seed == -1)
95     {
96       time_t t = time(NULL);
97       unsigned char *p = (unsigned char *)&t;
98       uint32_t s = 0;
99
100       /* time_t hashing based on code by Lawrence Kirby. */
101       for(size_t i = 0; i < sizeof t; i++) s = s * (UCHAR_MAX + 2U) + p[i];
102
103       if(options.random_device != NULL)
104       {
105         FILE *fp;
106         uint32_t temp;
107
108         fp = fopen(options.random_device, "r");
109         if(fp != NULL)
110         {
111           if(fread(&temp, sizeof temp, 1, fp) == 1) s = temp;
112           fclose(fp);
113         }
114       }
115
116       zterp_srand(s);
117     }
118     else
119     {
120       zterp_srand(options.random_seed);
121     }
122
123     rng_interval = 0;
124   }
125   else if(value < 1000)
126   {
127     rng_counter = 0;
128     rng_interval = value;
129   }
130   else
131   {
132     zterp_srand(value);
133     rng_interval = 0;
134   }
135 }
136
137 void zrandom(void)
138 {
139   long v = (int16_t)zargs[0];
140
141   if(v <= 0)
142   {
143     seed_random(-v);
144     store(0);
145   }
146   else
147   {
148     uint32_t res;
149
150     if(rng_interval != 0)
151     {
152       res = rng_counter++;
153       if(rng_counter == rng_interval) rng_counter = 0;
154     }
155     else
156     {
157       res = zterp_rand();
158     }
159
160     store(res % zargs[0] + 1);
161   }
162 }