はじめに
9日、11日、12日と3回に渡って乱数の紹介をしてきました。
ここからが本番です。
乱数マネージャの作成
過去3回の開設で、乱数には得手不得手がある事がわかりました。
アルゴリズム | メリット | デメリット |
---|---|---|
線形合同法 | 実装が簡単 | 周期が短い |
CNG Random | 暗号用途に使用可能 | Windows API なので他ではつかえない |
XorShift | 実装が簡単・高速 | とくになし |
Mersenne Twister | 周期がとてつもなく長い | 計算資源をそれなりに使う |
得手不得手に応じて、これらを簡単に切り替えられたら良いですよね。
TBitmapCodecManager のように、上記4つの乱数を切り替えられる、TRandomManager を作りましょう。
TRandomManager
TRandomManager の機能は
- 乱数アルゴリズムの登録
- 乱数アルゴリズムの呼び出し
とします。
実際の乱数は Random を呼んで実行するようにします。
(TBitmapCodecManager がコーデックを管理して、実際の使用は TBitmap を使う様な関係です)
登録機構を作る
乱数アルゴリズムは class として実装します。
(前回までは record で実装しました)
そのため、乱数アルゴリズムクラスを生成する機構が必要です。
そこで、乱数アルゴリズムクラスを生成する IRandomAlgoBuilder インターフェースを定義します。
IRandomAlgoBuilder = interface
['{4461DD5C-6BCF-4F3E-A93D-24ED90E157BB}']
function CreateAlgo: IRandomAlgo;
end;
この IRandomAlgoBuilder を TRandomManager に登録できるようにします。
必要になったら IRandomAlgoBuilder を呼び出して利用する乱数アルゴリズム本体を生成する仕組みです。
class procedure RegisterAlgo(
const AName: String; // 乱数アルゴリズムの名前
const ABuilder: IRandomAlgoBuilder // 乱数アルゴリズムを生成するビルダー
);
TXorShiftAlgoBuilder を登録する場合は↓のようになります。
initialization
TRandomManager.RegisterAlgo(TXorShiftAlgo.NAME, TXorShiftAlgoBuilder.Create);
IRandomAlgo の定義
各アルゴリズムが実装しなければいけないインターフェース IRandomAlgo を作成します。
IRandomAlgoBuilder.CreateAlgo はこれを実装したクラスを生成して返します。
IRandomAlgo = interface
['{6508CC6E-67CD-4F63-B4FF-5F6461F2FE76}']
procedure Initialize;
procedure SetSeed(const ASeed: UInt64);
function Execute: UInt32;
function GetName: String;
property Name: String read GetName;
end;
全ての乱数クラスが IRandomAlgo を実装します。
そして、そのたびに class(TInterfacedObject, IRandomAlgo)
と書くのが面倒なので、基底クラスを作りました。
IRandomAlgo のメソッドは全て abstract で定義されています。
TRandomAlgo = class(TInterfacedObject, IRandomAlgo)
protected
procedure Initialize; virtual; abstract;
procedure SetSeed(const ASeed: UInt64); virtual; abstract;
function Execute: UInt32; virtual; abstract;
function GetName: String; virtual; abstract;
public
property Name: String read GetName;
end;
これも TXorShiftAlgo を例に説明します。
TXorShiftAlgo = class(TRandomAlgo)
private const
NAME = 'XorShift';
private var
FA: UInt32;
private
procedure Initialize; override;
procedure SetSeed(const ASeed: UInt64); override;
function Execute: UInt32; override;
function GetName: String; override;
end;
TRandomAlgo クラスを継承してメソッドを実装しています。
function TXorShiftAlgo.Execute: UInt32;
begin
FA := FA xor (FA shl 13);
FA := FA xor (FA shr 17);
FA := FA xor (FA shl 5);
Result := FA;
end;
function TXorShiftAlgo.GetName: String;
begin
Result := NAME;
end;
procedure TXorShiftAlgo.Initialize;
begin
FA := Round(Frac(Now) * 24 * 60 * 60 * 1000) mod 1000;;
end;
procedure TXorShiftAlgo.SetSeed(const ASeed: UInt64);
begin
DefaultRandomize(ASeed);
FA := RandSeed;
end;
利用方法
最後に利用方法です。
PK.Math.Random.RandomManager を uses して各アルゴリズムを表す文字列でアルゴリズムを指定し、Random や Randomize を呼ぶだけです。
uses
PK.Math.Random.RandomManager;
procedure Sample;
begin
TRandomManager.Current.SetAlgo('XorShift');
Randomize;
Writeln(Random(10));
end;
今まで紹介してきたアルゴリズムが使えます。
アルゴリズム | 文字列 | Unit |
---|---|---|
線形合同法 | LCG | PK.Math.Random.LCG.pas |
CNG Random | CNG | PK.Math.Random.CNG.pas |
XorShift | XorShift | PK.Math.Random.XorShift.pas |
MersenneTwister | MT | PK.Math.Random.MersenneTwister.pas |
ソース
下記からダウンロードできます。
https://github.com/freeonterminate/RandomManager
まとめ
内部に全アルゴリズムを内包しているので、ちょっと効率が悪いですね…
何か良い方法はないかなと思った物の時間切れ…