master
1//! ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
2//!
3//! Follows the general idea of the implementation from here with a few shortcuts.
4//! https://doc.rust-lang.org/rand/src/rand/prng/isaac64.rs.html
5
6const std = @import("std");
7const mem = std.mem;
8const Isaac64 = @This();
9
10r: [256]u64,
11m: [256]u64,
12a: u64,
13b: u64,
14c: u64,
15i: usize,
16
17pub fn init(init_s: u64) Isaac64 {
18 var isaac = Isaac64{
19 .r = undefined,
20 .m = undefined,
21 .a = undefined,
22 .b = undefined,
23 .c = undefined,
24 .i = undefined,
25 };
26
27 // seed == 0 => same result as the unseeded reference implementation
28 isaac.seed(init_s, 1);
29 return isaac;
30}
31
32pub fn random(self: *Isaac64) std.Random {
33 return std.Random.init(self, fill);
34}
35
36fn step(self: *Isaac64, mix: u64, base: usize, comptime m1: usize, comptime m2: usize) void {
37 const x = self.m[base + m1];
38 self.a = mix +% self.m[base + m2];
39
40 const y = self.a +% self.b +% self.m[@as(usize, @intCast((x >> 3) % self.m.len))];
41 self.m[base + m1] = y;
42
43 self.b = x +% self.m[@as(usize, @intCast((y >> 11) % self.m.len))];
44 self.r[self.r.len - 1 - base - m1] = self.b;
45}
46
47fn refill(self: *Isaac64) void {
48 const midpoint = self.r.len / 2;
49
50 self.c +%= 1;
51 self.b +%= self.c;
52
53 {
54 var i: usize = 0;
55 while (i < midpoint) : (i += 4) {
56 self.step(~(self.a ^ (self.a << 21)), i + 0, 0, midpoint);
57 self.step(self.a ^ (self.a >> 5), i + 1, 0, midpoint);
58 self.step(self.a ^ (self.a << 12), i + 2, 0, midpoint);
59 self.step(self.a ^ (self.a >> 33), i + 3, 0, midpoint);
60 }
61 }
62
63 {
64 var i: usize = 0;
65 while (i < midpoint) : (i += 4) {
66 self.step(~(self.a ^ (self.a << 21)), i + 0, midpoint, 0);
67 self.step(self.a ^ (self.a >> 5), i + 1, midpoint, 0);
68 self.step(self.a ^ (self.a << 12), i + 2, midpoint, 0);
69 self.step(self.a ^ (self.a >> 33), i + 3, midpoint, 0);
70 }
71 }
72
73 self.i = 0;
74}
75
76fn next(self: *Isaac64) u64 {
77 if (self.i >= self.r.len) {
78 self.refill();
79 }
80
81 const value = self.r[self.i];
82 self.i += 1;
83 return value;
84}
85
86fn seed(self: *Isaac64, init_s: u64, comptime rounds: usize) void {
87 // We ignore the multi-pass requirement since we don't currently expose full access to
88 // seeding the self.m array completely.
89 @memset(self.m[0..], 0);
90 self.m[0] = init_s;
91
92 // prescrambled golden ratio constants
93 var a = [_]u64{
94 0x647c4677a2884b7c,
95 0xb9f8b322c73ac862,
96 0x8c0ea5053d4712a0,
97 0xb29b2e824a595524,
98 0x82f053db8355e0ce,
99 0x48fe4a0fa5a09315,
100 0xae985bf2cbfc89ed,
101 0x98f5704f6c44c0ab,
102 };
103
104 comptime var i: usize = 0;
105 inline while (i < rounds) : (i += 1) {
106 var j: usize = 0;
107 while (j < self.m.len) : (j += 8) {
108 comptime var x1: usize = 0;
109 inline while (x1 < 8) : (x1 += 1) {
110 a[x1] +%= self.m[j + x1];
111 }
112
113 a[0] -%= a[4];
114 a[5] ^= a[7] >> 9;
115 a[7] +%= a[0];
116 a[1] -%= a[5];
117 a[6] ^= a[0] << 9;
118 a[0] +%= a[1];
119 a[2] -%= a[6];
120 a[7] ^= a[1] >> 23;
121 a[1] +%= a[2];
122 a[3] -%= a[7];
123 a[0] ^= a[2] << 15;
124 a[2] +%= a[3];
125 a[4] -%= a[0];
126 a[1] ^= a[3] >> 14;
127 a[3] +%= a[4];
128 a[5] -%= a[1];
129 a[2] ^= a[4] << 20;
130 a[4] +%= a[5];
131 a[6] -%= a[2];
132 a[3] ^= a[5] >> 17;
133 a[5] +%= a[6];
134 a[7] -%= a[3];
135 a[4] ^= a[6] << 14;
136 a[6] +%= a[7];
137
138 comptime var x2: usize = 0;
139 inline while (x2 < 8) : (x2 += 1) {
140 self.m[j + x2] = a[x2];
141 }
142 }
143 }
144
145 @memset(self.r[0..], 0);
146 self.a = 0;
147 self.b = 0;
148 self.c = 0;
149 self.i = self.r.len; // trigger refill on first value
150}
151
152pub fn fill(self: *Isaac64, buf: []u8) void {
153 var i: usize = 0;
154 const aligned_len = buf.len - (buf.len & 7);
155
156 // Fill complete 64-byte segments
157 while (i < aligned_len) : (i += 8) {
158 var n = self.next();
159 comptime var j: usize = 0;
160 inline while (j < 8) : (j += 1) {
161 buf[i + j] = @as(u8, @truncate(n));
162 n >>= 8;
163 }
164 }
165
166 // Fill trailing, ignoring excess (cut the stream).
167 if (i != buf.len) {
168 var n = self.next();
169 while (i < buf.len) : (i += 1) {
170 buf[i] = @as(u8, @truncate(n));
171 n >>= 8;
172 }
173 }
174}
175
176test "sequence" {
177 var r = Isaac64.init(0);
178
179 // from reference implementation
180 const seq = [_]u64{
181 0xf67dfba498e4937c,
182 0x84a5066a9204f380,
183 0xfee34bd5f5514dbb,
184 0x4d1664739b8f80d6,
185 0x8607459ab52a14aa,
186 0x0e78bc5a98529e49,
187 0xfe5332822ad13777,
188 0x556c27525e33d01a,
189 0x08643ca615f3149f,
190 0xd0771faf3cb04714,
191 0x30e86f68a37b008d,
192 0x3074ebc0488a3adf,
193 0x270645ea7a2790bc,
194 0x5601a0a8d3763c6a,
195 0x2f83071f53f325dd,
196 0xb9090f3d42d2d2ea,
197 };
198
199 for (seq) |s| {
200 try std.testing.expect(s == r.next());
201 }
202}
203
204test fill {
205 var r = Isaac64.init(0);
206
207 // from reference implementation
208 const seq = [_]u64{
209 0xf67dfba498e4937c,
210 0x84a5066a9204f380,
211 0xfee34bd5f5514dbb,
212 0x4d1664739b8f80d6,
213 0x8607459ab52a14aa,
214 0x0e78bc5a98529e49,
215 0xfe5332822ad13777,
216 0x556c27525e33d01a,
217 0x08643ca615f3149f,
218 0xd0771faf3cb04714,
219 0x30e86f68a37b008d,
220 0x3074ebc0488a3adf,
221 0x270645ea7a2790bc,
222 0x5601a0a8d3763c6a,
223 0x2f83071f53f325dd,
224 0xb9090f3d42d2d2ea,
225 };
226
227 for (seq) |s| {
228 var buf0: [8]u8 = undefined;
229 var buf1: [7]u8 = undefined;
230 std.mem.writeInt(u64, &buf0, s, .little);
231 r.fill(&buf1);
232 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
233 }
234}