-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMathBase.Random.pas
More file actions
185 lines (160 loc) · 4.95 KB
/
Copy pathMathBase.Random.pas
File metadata and controls
185 lines (160 loc) · 4.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
unit MathBase.Random;
{-----------------------------------------------------------------------------
MathBase.Random
Explicit local random state for reproducible numerical workflows. The
generator never reads or writes the RTL global RandSeed.
-----------------------------------------------------------------------------}
{$mode objfpc}{$H+}{$J-}{$Q-}
{$modeswitch advancedrecords}
interface
uses
SysUtils, Math;
type
ERandomStateError = class(Exception);
TRandomState = record
Words: array[0..3] of QWord;
end;
{ xoshiro256** with SplitMix64 seeding. This generator is intended for
simulation and sampling, not cryptography. Value assignment copies the
complete state and subsequent mutation is independent. }
TLocalRandom = record
private
FState: TRandomState;
class function RotateLeft(const Value: QWord; const Shift: Byte): QWord;
static; inline;
class function SplitMix64(var State: QWord): QWord; static; inline;
procedure Jump;
public
class function Seeded(const Seed: QWord): TLocalRandom; static;
procedure Reseed(const Seed: QWord);
function NextUInt64: QWord;
function NextUInt32: LongWord;
function NextDouble: Double;
function NextSingle: Single;
function NextInteger(const UpperExclusive: SizeUInt): SizeUInt;
function NextNormal: Double;
function Split: TLocalRandom;
function GetState: TRandomState;
procedure SetState(const State: TRandomState);
end;
implementation
class function TLocalRandom.RotateLeft(const Value: QWord;
const Shift: Byte): QWord;
begin
Result := (Value shl Shift) or (Value shr (64 - Shift));
end;
class function TLocalRandom.SplitMix64(var State: QWord): QWord;
var
Z: QWord;
begin
State := State + QWord($9E3779B97F4A7C15);
Z := State;
Z := (Z xor (Z shr 30)) * QWord($BF58476D1CE4E5B9);
Z := (Z xor (Z shr 27)) * QWord($94D049BB133111EB);
Result := Z xor (Z shr 31);
end;
class function TLocalRandom.Seeded(const Seed: QWord): TLocalRandom;
begin
Result.Reseed(Seed);
end;
procedure TLocalRandom.Reseed(const Seed: QWord);
var
Seeder: QWord;
I: Integer;
begin
Seeder := Seed;
for I := 0 to 3 do
FState.Words[I] := SplitMix64(Seeder);
end;
function TLocalRandom.NextUInt64: QWord;
var
Temporary: QWord;
begin
Result := RotateLeft(FState.Words[1] * 5, 7) * 9;
Temporary := FState.Words[1] shl 17;
FState.Words[2] := FState.Words[2] xor FState.Words[0];
FState.Words[3] := FState.Words[3] xor FState.Words[1];
FState.Words[1] := FState.Words[1] xor FState.Words[2];
FState.Words[0] := FState.Words[0] xor FState.Words[3];
FState.Words[2] := FState.Words[2] xor Temporary;
FState.Words[3] := RotateLeft(FState.Words[3], 45);
end;
function TLocalRandom.NextUInt32: LongWord;
begin
Result := LongWord(NextUInt64 shr 32);
end;
function TLocalRandom.NextDouble: Double;
begin
{ The high 53 bits map exactly to [0, 1) in binary64. }
Result := (NextUInt64 shr 11) * (1.0 / 9007199254740992.0);
end;
function TLocalRandom.NextSingle: Single;
begin
Result := Single((NextUInt64 shr 40) * (1.0 / 16777216.0));
end;
function TLocalRandom.NextInteger(const UpperExclusive: SizeUInt): SizeUInt;
var
Candidate, Bound, Limit: QWord;
begin
if UpperExclusive = 0 then
raise ERandomStateError.Create(
'TLocalRandom.NextInteger: UpperExclusive must be positive.');
Bound := QWord(UpperExclusive);
Limit := High(QWord) - (High(QWord) mod Bound);
repeat
Candidate := NextUInt64;
until Candidate < Limit;
Result := SizeUInt(Candidate mod Bound);
end;
function TLocalRandom.NextNormal: Double;
var
U1, U2: Double;
begin
repeat
U1 := NextDouble;
until U1 > 0.0;
U2 := NextDouble;
Result := Sqrt(-2.0 * Ln(U1)) * Cos(2.0 * Pi * U2);
end;
procedure TLocalRandom.Jump;
const
JumpPolynomial: array[0..3] of QWord = (
QWord($180EC6D33CFD0ABA), QWord($D5A61266F0C9392C),
QWord($A9582618E03FC9AA), QWord($39ABDC4529B1661C));
var
Accumulated: TRandomState;
I, BitIndex: Integer;
begin
FillChar(Accumulated, SizeOf(Accumulated), 0);
for I := 0 to 3 do
for BitIndex := 0 to 63 do
begin
if (JumpPolynomial[I] and (QWord(1) shl BitIndex)) <> 0 then
begin
Accumulated.Words[0] := Accumulated.Words[0] xor FState.Words[0];
Accumulated.Words[1] := Accumulated.Words[1] xor FState.Words[1];
Accumulated.Words[2] := Accumulated.Words[2] xor FState.Words[2];
Accumulated.Words[3] := Accumulated.Words[3] xor FState.Words[3];
end;
NextUInt64;
end;
FState := Accumulated;
end;
function TLocalRandom.Split: TLocalRandom;
begin
Result.FState := FState;
Jump;
end;
function TLocalRandom.GetState: TRandomState;
begin
Result := FState;
end;
procedure TLocalRandom.SetState(const State: TRandomState);
begin
if (State.Words[0] = 0) and (State.Words[1] = 0) and
(State.Words[2] = 0) and (State.Words[3] = 0) then
raise ERandomStateError.Create(
'TLocalRandom.SetState: the all-zero generator state is invalid.');
FState := State;
end;
end.