A simple implementation of the Mersenne twister
TMersenne32
When you look at the source code that implements Free Pascal's random
it may look overwelmingly complex.
The algorithm itself, though, is very understandable.
Here is a simple 32 bit implementation according to MT19937. It is compatible with FPC's Random for 32 bit values.
You may actually prefer this one over the one in system. That is because system is usually compiled with conservative optimization settings.
Although the core implementation is about the same, you can compile this with more agressive compiler optimization settings.
E.g. on armhf I got a 50% better performance using fpc -CX -XXs -OpARMV7a -CpARMV7A -CfVFPV4 -Sv -OoFASTMATH -O4 -B mersenne.pas
E.g. on AMD64 I got a 30% better performance using fpc -CX -XXs -OpATHLON64 -CpATHLON64 -CfSSE41 -Sv -OoFASTMATH -B mersenne.pas
The performance gain is solely because of the compiler settings.
Enjoy,
Thaddy.
It is verified against several mt19937 implementations in different languages, including Freepascal and C/C++.
Of course I added this one to wikipedia too. You don't have to create this class. it is fully static.
Simply call Rnd32.URand, URandf, Srand of Srandf to obtain a random value.
{ new version 2017-02-14 }
unit mersenne;
{$mode objfpc}{$A8}{$R-}{$Q-}
interface
type
TMersenne32 = class sealed
strict private
const
// Define MT19937 constants (32-bit RNG)
N = 624;M = 397;R = 31;A = $9908B0DF;F = 1812433253;
U = 11;S = 7;B = $9D2C5680;T = 15;C = $EFC60000;L = 18;
MASK_LOWER = (QWORD(1) << R) - 1;
MASK_UPPER = QWORD(1) << R;
class var mt:array[0..N-1] of dword;
class var index:word;
class procedure twist;inline;static;
public
class constructor create;
class procedure initialize(const seed:dword);inline;static;
{ 32 bit unsigned integer }
class function URand:dword;inline;static;
{ 32 bit float in range 0..1 }
class function URandf:single;inline;static;
{ 32 bit signed integer }
class function SRand:integer;inline;static;
{32 bit float in the range -1..1 }
class function SRandf:single;inline;static;
end;
Rnd32 = type TMersenne32;
implementation
class constructor TMersenne32.Create;
begin
initialize(5489); // default seed for mt199937
end;
class procedure TMersenne32.Initialize(const seed:dword);inline;static;
var
i:dword;
begin
mt[0] := seed;
for i := 1 to pred(N) do
mt[i] := F * (mt[i - 1] xor (mt[i - 1] >> 30)) + i;
index := N;
end;
class procedure TMersenne32.Twist;inline;static;
var
i:integer;
begin
for i:=0 to N-M-1 do
mt[i]:=mt[i+M] xor {twist} (((mt[i] and MASK_UPPER) or
(mt[i+1] and MASK_LOWER)) shr 1)xor(dword(-(mt[i+1] and 1)) and A);
for i:=N-M to N-2 do
mt[i]:=mt[i+(M-N)]xor{twist}(((mt[i] and MASK_UPPER) or
(mt[i+1] and MASK_LOWER)) shr 1)xor(dword(-(mt[i+1] and 1)) and A);
mt[N-1]:=mt[M-1] xor {twist} (((mt[n-1] and MASK_UPPER) or (mt[0] and
MASK_LOWER)) shr 1)xor(dword(-(mt[0] and 1)) and A);
index:=0;
end;
class function TMersenne32.URand:dword;inline;static;
var
i:integer;
begin
i := index;
if index >= N then
begin
Twist;
i := index;
end;
Result := mt[i];index := i + 1;
Result := Result xor (mt[i] >> U);
Result := Result xor (Result << S) and B;
Result := Result xor (Result << T) and C;
Result := Result xor(Result >> L);
end;
class function TMersenne32.URandf:single;inline;static;
begin
Result := URand * 2.32830643653869628906e-10;
end;
class function TMersenne32.SRandf:single;inline;static;
begin
Result :=URand * 4.6566128730773926e-010;
end;
class function TMersenne32.SRand:integer;inline;static;
begin
{$ifopt D+}Result := 0;{$endif}
Result := URand;
end;
end.
C++ verification test:
This code is for a 32 bit float (single) and can be used to check against FPC's Random as well, provided you cast the output to single:
Compile this with g++ mt_cpp_float32.cpp -std=gnu++11 -o mt_cpp_float32
#include <iostream>
#include <random>
using namespace std;
int main()
{
mt19937 mt_rand(5489ul);
for(int i=0;i<5;i++){
cout << (float) mt_rand() * 2.32830643653869628906e-10 << endl;};
return 0;
}
Compile this with g++ mt_cpp_uint32.cpp -std=gnu++11 -o mt_cpp_uint32
Run ./mersplusplus and compare the output with TMersenne32. This code is for 32 bit unsigned. It should be (is) equal.
#include <iostream>
#include <random>
using namespace std;
int main()
{
mt19937 mt_rand(5489u);
for(int i=0;i<5;i++){
cout << mt_rand() << endl;};
return 0;
}
Verification against FPC's Random
program mersennetest2;
{$mode objfpc}
uses sysutils,dateutils,mersenne;
var
i:integer;
begin
Randseed :=5489; // default seed for mt199937
for i := 0 to 9999999 do Rnd32.urandf;write(Rnd32.urandf :2:10,' ');writeln('Mersenne32');
for i := 0 to 9999999 do Random ;write(single(Random) :2:10,' ');writeln('FPC Random');
end.