Difference between revisions of "A simple implementation of the Mersenne twister"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(40 intermediate revisions by 2 users not shown)
Line 1: Line 1:
=== A simple implementation of the Mersenne twister ===
+
=== TMersenne32 ===
When you look at the sourcecode that implements Freepascal's '''random''' it may look overwelmingly complex.
+
When you look at the source code that implements Free Pascal's <code>[[doc:rtl/system/random.html|random]]</code> it may look overwelmingly complex.
The algorithm itself, though, is very understandable. Here is simple implementation according to MT19937.<br>
+
The algorithm itself, though, is very understandable.<br>
Enjoy, <br>Thaddy.<br><br>
+
Here is a simple 32 bit implementation according to MT19937. It is compatible with FPC's Random for 32 bit values.<br>  
It is basically taken from the C version in wikipedia. Of course I added this one there too.
+
You may actually prefer this one over the one in system. That is because system is usually compiled with conservative optimization settings.<br>
You don't have to create this class. it is fully static. Simple call TMersenne.ExtractU32 to obtain a random value.<br>
+
Although the core implementation is about the same, you can compile this with more agressive compiler optimization settings.<br>
<syntaxhighlight>
+
E.g. on armhf I got a 50% better performance using fpc  -CX -XXs -OpARMV7a -CpARMV7A -CfVFPV4 -Sv -OoFASTMATH -O4  -B mersenne.pas<br>
 +
E.g. on AMD64 I got a 30% better performance using fpc -CX -XXs -OpATHLON64 -CpATHLON64 -CfSSE41 -Sv -OoFASTMATH -B mersenne.pas<br>
 +
The performance gain is solely because of the compiler settings.
 +
 
 +
Enjoy,<br/>
 +
Thaddy.
 +
 
 +
It is verified against several mt19937 implementations in different languages, including Freepascal and C/C++.<br>
 +
 
 +
Of course I added this one to wikipedia too. You don't have to create this class. it is fully static.<br>
 +
Simply call Rnd32.URand, URandf, Srand of Srandf to obtain a random value.
 +
 
 +
 
 +
<syntaxhighlight lang=pascal>
 +
{ new version 2017-02-14 }
 
unit mersenne;
 
unit mersenne;
{$mode objfpc}
+
{$mode objfpc}{$A8}{$R-}{$Q-}
 
interface
 
interface
 
type
 
type
   TMersenne = class sealed
+
   TMersenne32 = class sealed
 
   strict private
 
   strict private
 
   const  
 
   const  
Line 24: Line 38:
 
     class constructor create;
 
     class constructor create;
 
     class procedure initialize(const seed:dword);inline;static;
 
     class procedure initialize(const seed:dword);inline;static;
     class function Extractu32: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;
 
   end;
 
+
 
 +
  Rnd32 = type TMersenne32;
 +
 
 
implementation  
 
implementation  
  
class constructor TMersenne.Create;
+
class constructor TMersenne32.Create;
 
begin
 
begin
   initialize(12345678);
+
   initialize(5489); // default seed for mt199937
 
end;
 
end;
  
class procedure TMersenne.Initialize(const seed:dword);inline;static;
+
class procedure TMersenne32.Initialize(const seed:dword);inline;static;
 
var  
 
var  
 
   i:dword;
 
   i:dword;
Line 44: Line 67:
 
end;
 
end;
 
    
 
    
class procedure TMersenne.Twist;inline;static;
+
class procedure TMersenne32.Twist;inline;static;
 
var  
 
var  
   i,x,xA:dword;
+
   i:integer;
 
begin
 
begin
   for i := 0 to pred(N) do
+
   for i:=0 to N-M-1 do
  begin
+
     mt[i]:=mt[i+M] xor {twist} (((mt[i] and MASK_UPPER) or
     x := (mt[i] and MASK_UPPER) + (mt[(i + 1) mod N] and MASK_LOWER);
+
    (mt[i+1] and MASK_LOWER)) shr 1)xor(dword(-(mt[i+1] and 1)) and A);
     xA := x >> 1;
+
  for i:=N-M to N-2 do
     if ( x and $1 ) <> 0 then
+
     mt[i]:=mt[i+(M-N)]xor{twist}(((mt[i] and MASK_UPPER) or
  xA := xA xor A;
+
     (mt[i+1] and MASK_LOWER)) shr 1)xor(dword(-(mt[i+1] and 1)) and A);
     mt[i] := mt[(i + M) mod N] xor xA;
+
     mt[N-1]:=mt[M-1] xor {twist} (((mt[n-1] and MASK_UPPER) or (mt[0] and
  end;
+
    MASK_LOWER)) shr 1)xor(dword(-(mt[0] and 1)) and A);
   index := 0;
+
   index:=0;
 
end;
 
end;
  
class function TMersenne.ExtractU32:dword;inline;static;
+
 
 +
class function TMersenne32.URand:dword;inline;static;
 
var
 
var
  y:dword;
 
 
   i:integer;
 
   i:integer;
 
begin
 
begin
Line 70: Line 93:
 
     i := index;
 
     i := index;
 
   end;
 
   end;
   y := mt[i];
+
   Result := mt[i];index := i + 1;
  index := i + 1;
+
   Result := Result xor (mt[i] >> U);
   y := y xor (mt[i] >> U);
+
   Result := Result xor (Result << S) and B;
   y := y xor (y << S) and B;
+
   Result := Result xor (Result << T) and C;
   y := y xor (y << T) and C;
+
   Result := Result xor(Result >> L);
   y := y xor(y >> L);
+
end;
   Result :=y;
+
 
 +
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;
 +
end. 
 +
</syntaxhighlight>
  
end.</syntaxhighlight>
+
=== C++ verification test: ===
===reference ===
+
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:<br>
 +
Compile this with g++ mt_cpp_float32.cpp -std=gnu++11 -o mt_cpp_float32
 +
<source lang="c">
 +
#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;
 +
}
 +
</source>
 +
Compile this with g++ mt_cpp_uint32.cpp -std=gnu++11 -o mt_cpp_uint32<br>
 +
Run ./mersplusplus and compare the output with TMersenne32. This code is for 32 bit unsigned. It should be (is) equal.
 +
<source lang="c">
 +
#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;
 +
}
 +
</source>
 +
Left FPC, right C++<br>
 +
[[File:Selection_019.png]]
 +
 
 +
=== Verification against FPC's Random ===
 +
<syntaxhighlight lang=pascal>
 +
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.
 +
</syntaxhighlight>
 +
 
 +
== Reference ==
 
#[https://en.wikipedia.org/wiki/Mersenne_Twister Mersenne twister in WikiPedia]
 
#[https://en.wikipedia.org/wiki/Mersenne_Twister Mersenne twister in WikiPedia]
 +
 +
== See also ==
 +
* [[Dev random]]
 +
* [[Functions for descriptive statistics]]
 +
* [[Generating Random Numbers]]
 +
* [[Marsaglia's pseudo random number generators]]
 +
 +
[[Category:Statistical algorithms]]
 +
[[Category:Modelling and Simulation]]
 +
[[Category: Code]]

Latest revision as of 08:55, 1 February 2020

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;
}

Left FPC, right C++
Selection 019.png

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.

Reference

  1. Mersenne twister in WikiPedia

See also