2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Custom Fast ThreadSafe Random Number Generator for Freepascal
6
Implementation of XorShift algorithm for random numbers generation.
7
(based on the "castle game engine"'s random number generator : https://castle-engine.sourceforge.io)
8
In some cases it works 2 to 3 times faster than native FPC random function.
11
- https://en.wikipedia.org/wiki/Linear_congruential_generator
12
- https://en.wikipedia.org/wiki/Xorshift
13
- https://en.wikipedia.org/wiki/Multiply-with-carry
15
Unit GLRandomGenerator;
27
- XorShif64 : Très rapide
28
- rngtMWC256 : Moins rapide mais plus précis
30
TGLRandomNumGeneratorType = (rngtXorShift64, rngtMWC256); //rngtXorShift128, rngtXorShift1024,
31
TGLRandomNumGenerator = class
33
// cSeed, iSeed : DWord;
34
// QSeed : Array[0..$FF] of DWord;
36
FConstantSeed:Longint;
37
FUseConstantSeed : Boolean;
38
FRNGType : TGLRandomNumGeneratorType;
40
function GetSeed: Longint; inline;
41
procedure SetSeed(AValue: Longint);inline;
42
procedure Initialize(const ASeed:Cardinal);inline;
44
procedure XorShift;inline;
45
//class function GetRandomSeed: LongInt;
50
procedure Randomize;inline;
51
function Random: extended; overload;inline;
52
function RandomInt: LongWord; inline;
53
function Random(range:longint):longint;overload;inline;
54
function Random(range:int64):int64;overload;inline;
56
property RandSeed:Longint Read GetSeed write SetSeed;
57
//property UseConstantSeed : Boolean read FUseConstantSeed write FUseConstantSeed;
61
GLS_RNG : TGLRandomNumGenerator;
65
//CONVERT_MWC256_TO_FLOAT = 2.32830643653869E-10; //1/MaxInt; // Extended (1.0/int64(1 shl 32)); // 0..1
66
CONVERT_TO_FLOAT = 1/MaxInt;
68
// CONVERT_SIGNED = Extended (2.0/int64(1 shl 32)); // -1..1
72
// GLRNG_RandSeed:Longint;
73
// GLRNG_OldRandSeed:Cardinal;
75
store_64bit_seed: QWord = 0; //this variable stores 64 bit seed for reusing
76
wait_for_seed: boolean = false;
78
function TGLRandomNumGenerator.GetSeed: Longint;
80
Result := FConstantSeed; //GLRNG_RandSeed;
83
procedure TGLRandomNumGenerator.SetSeed(AValue: Longint);
85
FConstantSeed := AValue;
87
//Initialize(GLRNG_Randseed);
90
procedure TGLRandomNumGenerator.Initialize(const ASeed: Cardinal);
98
//QSeed[i]:=GLRNG_randseed; //RandSeed;
100
// GLRNG_RandSeed := GetRandomSeed;
105
FRandSeed := LongInt(ASeed);
106
FConstantSeed := FRandSeed;
110
procedure TGLRandomNumGenerator.ResetSeed;
112
FRandSeed := FConstantSeed;
115
constructor TGLRandomNumGenerator.create;
120
//GLRNG_OldRandSeed := 0;
123
(*RandSeed := ((RandSeed shl 8) or GetCurrentProcessID) xor
125
procedure TGLRandomNumGenerator.Randomize;
127
date_multiplier: QWord = 30000000; // approximative de la date
128
date_order: QWord = 80000 * 30000000; // ordre : "now*date_multiplier" variable
129
{p.s. date_order sera juste jusqu'à l'année ~ 2119}
131
var c64: QWord; // graine actuelle;
132
b64: QWord; // graine supplémentaire pour la sécurité multi-threading
133
a64: QWord; // une autre graine supplémentaire
135
hours, mins, secs, msecs : Word;
136
procedure xorshift64;
138
c64:=c64 xor (c64 shl 12);
139
c64:=c64 xor (c64 shr 25);
140
c64:=c64 xor (c64 shl 27);
143
{Nous ajoutons une variable semi-aléatoire supplémentaire basée sur la variable locale c64 :
144
son adresse 64 bits. Le seul bénéfice que nous avons ici est que cette adresse sera
145
différent pour les différents threads, donc 2 threads ne peuvent être initialisés
146
avec des graines égales, même si elles sont absolument simultanées }
148
c64 := QWORD(@(c64));
149
DecodeTime(Now,Hours,mins,Secs,msecs);
153
Hours := Hours * 3600;
155
a64:=Hours+ Mins + Secs + msecs;
158
while wait_for_seed do
160
//DecodeTime(Now,Hour,mins,Secs,msecs);
161
//Secs := Secs * 100;
163
// Hour := Hour * 3600;
165
xorshift64; //En attendant, on fait quelque chose
168
wait_for_seed := true; // Empêche une autre randomisation de commencer jusqu'à ce que celui-ci est fini
170
c64 := (c64 + a64) shr 1;
171
c64 := ((c64 shl 8) or a64) xor GetTickCount64;
173
b64 := c64; // notre autre graine aléatoire basée sur l'allocation de la mémoire de thread en cours
175
{fondamentalement, nous ne nous soucions pas si les threads passeront accidentellement
176
'wait_for_seed' le verrouille.
177
Grâce à b64 nous aurons des valeurs aléatoires différentes, mais ce n'est peut être pas optimal }
179
if store_64bit_seed = 0 then
180
begin //1ere randomization
183
(* DecodeTime(Now,Hour,mins,Secs,msecs);
187
a64:=Hour + Mins + Secs + msecs;
188
c64 := (c64 + a64) shr 1; *)
190
{ Maintenant, nous devons nous assurer que l'ajout de 'a64' ne débordera pas
191
Nous ajoutons quelques xorshift64 juste pour le plaisir au cas où}
192
while (c64 > high(QWord)-date_order) do xorshift64;
194
{ Pour tuer la valeur discrette aléatoire introduit par gettickcount64 nous ajoutons 'Now'.
195
'now' et 'gettickcount64' ne sont pas indépendants mais changent synchroniquement.
196
Après plusieurs xorshift64, c64 n'a plus aucune information
197
laissé par gettickcount64 et nous introduisons un changement semi-indépendant dans la graine aléatoire}
198
c64 := c64+ QWord(round(now*date_multiplier));
200
{ Un autre cycle xorshift de 64 bits pour tuer tout ce qui reste 'Now' }
202
{ Maintenant nous sommes sûrs d'obtenir une graine aléatoire différente même
203
dans le cas où nous lancons la procédure exactement à la même milliseconde depuis
204
le démarrage de l'OS.
205
Une date et heure différentes donneront une autre graine aléatoire ...
206
A moins de fixer délibérément la date et l'heure }
211
c64 := store_64bit_seed; //On reprend juste la graine déja générer
213
// c64 := c64 shr 1; // note: nous jetons 1 bit de précision pour gagner de la vitesse
214
{ Maintenant, nous faisons juste un autre xorshift64, car nous avons une variable c64 aléatoire correcte }
216
{On fusionne une autre variable aléatoire basée sur le thread en cours }
219
{et pour finir, afin d"éviter d'avoir une graine à ZERO}
221
{Quelques xorshift64 de plus}
223
{On garde les 32-bits haut de c64 pour avoir une veribale graine 64bits}
224
FConstantSeed := longint(c64 shr 32);
225
until FConstantSeed<>0;
226
// FConstantSeed := FConstantSeed shr 1;
227
FRandSeed := FConstantSeed;
228
{ On sauvegarde notre graine pour une réutilisation ultérieur au cas ou }
229
store_64bit_seed := c64;
230
{On passe la main au prochain thread}
231
wait_for_seed := false;
234
procedure TGLRandomNumGenerator.XorShift; inline;
236
// FRandSeed := FRandSeed shr 1;
237
{ Ffonctionne un peu plus vite (+ 4%) en raison d'une meilleure optimisation
238
par compilateur (utilise des registres de CPU au lieu d'une variable) }
239
FRandSeed := ((FRandSeed xor (FRandSeed shl 1)) xor ((FRandSeed xor (FRandSeed shl 1)) shr 15)) xor
240
(((FRandSeed xor (FRandSeed shl 1)) xor ((FRandSeed xor (FRandSeed shl 1)) shr 15)) shl 4);
242
(* FRandSeed:= FRandSeed xor (FRandSeed shl 1);
243
FRandSeed:= FRandSeed xor (FRandSeed shr 15);
244
FRandSeed :=FRandSeed xor (FRandSeed shl 4); *)
250
function TGLRandomNumGenerator.Random: extended; Inline;
253
// MWC256 from Usenet posting by G. Marsaglia - Period 2^8222
254
(* iSeed := (iSeed+1) AND $FF;
255
tSeed := qword (809430660) * QSeed[iSeed] + cSeed;
257
QSeed[iSeed] := lo (tSeed);
258
result := CONVERT_TO_FLOAT*(QSeed[iSeed] shr 1); *)
261
result := CONVERT_TO_FLOAT*Longint(FRandSeed shr 1); // note: nous jetons 1 bit de précision pour gagner de la vitesse
264
function TGLRandomNumGenerator.Random(range: longint): longint;
268
result := LongInt((int64(LongWord(FRandSeed))*range) shr 32) // Plus rapide que FRandSeed Mod Range
273
function TGLRandomNumGenerator.RandomInt: LongWord;
276
result := LongWord(FRandSeed);
279
function TGLRandomNumGenerator.Random(range: int64): int64;
281
procedure xorshift64; inline;
283
c64:=c64 xor (c64 shl 12);
284
c64:=c64 xor (c64 shr 25);
285
c64:=c64 xor (c64 shl 27);
288
{Même si N = 0..1 pour faire un cycle de semences aléatoires de 32 bits nous devons le faire deux fois}
289
c64 := qword(RandomInt) or (qword(RandomInt) shl 32);
292
{l'ajout d'un cycle xorshift64 nous garantit que c64 est vraiment aléatoire
293
dans la plage 1..high (QWORD) mais ralentit l'exécution de ~ 10%}
295
{Contrairement à SysUtils nous en faisons un vrai nombre aléatoire de 64-bit et non pas un faux de 63 bits :)
296
Il ne peut pas y avoir de débordement ici, parce que N est int64 et il ne peut pas être
297
plus grand que (Hi(QWORD) div 2)
298
C'est-à-dire que nous ne pourrons jamais obtenir un résultat 'négatif' car le premier bit du résultat sera toujours zéro }
299
result := int64(qword(c64) mod qword(Range))
306
GLS_RNG := TGLRandomNumGenerator.create;