| 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 | |
| 6 | const std = @import("std"); |
| 7 | const mem = std.mem; |
| 8 | const Isaac64 = @This(); |
| 9 | |
| 10 | r: [256]u64, |
| 11 | m: [256]u64, |
| 12 | a: u64, |
| 13 | b: u64, |
| 14 | c: u64, |
| 15 | i: usize, |
| 16 | |
| 17 | pub 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 | |
| 32 | pub fn random(self: *Isaac64) std.Random { |
| 33 | return std.Random.init(self, fill); |
| 34 | } |
| 35 | |
| 36 | fn 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 | |
| 47 | fn 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 | |
| 76 | fn 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 | |
| 86 | fn 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 | |
| 152 | pub 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 | |
| 176 | test "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 | |
| 204 | test 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 | } |