master
1// Website: romu-random.org
2// Reference paper: http://arxiv.org/abs/2002.11331
3// Beware: this PRNG is trivially predictable. While fast, it should *never* be used for cryptographic purposes.
4
5const std = @import("std");
6const math = std.math;
7const RomuTrio = @This();
8
9x_state: u64,
10y_state: u64,
11z_state: u64, // set to nonzero seed
12
13pub fn init(init_s: u64) RomuTrio {
14 var x = RomuTrio{ .x_state = undefined, .y_state = undefined, .z_state = undefined };
15 x.seed(init_s);
16 return x;
17}
18
19pub fn random(self: *RomuTrio) std.Random {
20 return std.Random.init(self, fill);
21}
22
23fn next(self: *RomuTrio) u64 {
24 const xp = self.x_state;
25 const yp = self.y_state;
26 const zp = self.z_state;
27 self.x_state = 15241094284759029579 *% zp;
28 self.y_state = yp -% xp;
29 self.y_state = std.math.rotl(u64, self.y_state, 12);
30 self.z_state = zp -% yp;
31 self.z_state = std.math.rotl(u64, self.z_state, 44);
32 return xp;
33}
34
35pub fn seedWithBuf(self: *RomuTrio, buf: [24]u8) void {
36 const seed_buf = @as([3]u64, @bitCast(buf));
37 self.x_state = seed_buf[0];
38 self.y_state = seed_buf[1];
39 self.z_state = seed_buf[2];
40}
41
42pub fn seed(self: *RomuTrio, init_s: u64) void {
43 // RomuTrio requires 192-bits of seed.
44 var gen = std.Random.SplitMix64.init(init_s);
45
46 self.x_state = gen.next();
47 self.y_state = gen.next();
48 self.z_state = gen.next();
49}
50
51pub fn fill(self: *RomuTrio, buf: []u8) void {
52 var i: usize = 0;
53 const aligned_len = buf.len - (buf.len & 7);
54
55 // Complete 8 byte segments.
56 while (i < aligned_len) : (i += 8) {
57 var n = self.next();
58 comptime var j: usize = 0;
59 inline while (j < 8) : (j += 1) {
60 buf[i + j] = @as(u8, @truncate(n));
61 n >>= 8;
62 }
63 }
64
65 // Remaining. (cuts the stream)
66 if (i != buf.len) {
67 var n = self.next();
68 while (i < buf.len) : (i += 1) {
69 buf[i] = @as(u8, @truncate(n));
70 n >>= 8;
71 }
72 }
73}
74
75test "sequence" {
76 // Unfortunately there does not seem to be an official test sequence.
77 var r = RomuTrio.init(0);
78
79 const seq = [_]u64{
80 16294208416658607535,
81 13964609475759908645,
82 4703697494102998476,
83 3425221541186733346,
84 2285772463536419399,
85 9454187757529463048,
86 13695907680080547496,
87 8328236714879408626,
88 12323357569716880909,
89 12375466223337721820,
90 };
91
92 for (seq) |s| {
93 try std.testing.expectEqual(s, r.next());
94 }
95}
96
97test fill {
98 // Unfortunately there does not seem to be an official test sequence.
99 var r = RomuTrio.init(0);
100
101 const seq = [_]u64{
102 16294208416658607535,
103 13964609475759908645,
104 4703697494102998476,
105 3425221541186733346,
106 2285772463536419399,
107 9454187757529463048,
108 13695907680080547496,
109 8328236714879408626,
110 12323357569716880909,
111 12375466223337721820,
112 };
113
114 for (seq) |s| {
115 var buf0: [8]u8 = undefined;
116 var buf1: [7]u8 = undefined;
117 std.mem.writeInt(u64, &buf0, s, .little);
118 r.fill(&buf1);
119 try std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
120 }
121}
122
123test "buf seeding test" {
124 const buf0 = @as([24]u8, @bitCast([3]u64{ 16294208416658607535, 13964609475759908645, 4703697494102998476 }));
125 const resulting_state = .{ .x = 16294208416658607535, .y = 13964609475759908645, .z = 4703697494102998476 };
126 var r = RomuTrio.init(0);
127 r.seedWithBuf(buf0);
128 try std.testing.expect(r.x_state == resulting_state.x);
129 try std.testing.expect(r.y_state == resulting_state.y);
130 try std.testing.expect(r.z_state == resulting_state.z);
131}