xoroshiro 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 397fae7c723830610f2cc562c915dd25096641940f91feb082f3d48237da6d67
4
+ data.tar.gz: a1a95dd77003cfb69254432c2639f87934a3950314b218e3a46b1bab970848a6
5
+ SHA512:
6
+ metadata.gz: b90078aa55ad0b2c093ca1f7bbc1cb2953dea20124bd76574da198428d4714f262a97d5b8758ce42596bcd4afd6ee33853ad4f5c17a400d57cb99aa0762f5cd3
7
+ data.tar.gz: adee3262f19628655e14a30add4d4f8e08a9256da9657cdda879e08fc2d2e7d827d2a0dd9f4465de80a00a55c3e5911c4d7142696906466cc8e3a0f72a1b43c2
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+
5
+ Style/StringLiterals:
6
+ Enabled: true
7
+ EnforcedStyle: single_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ Enabled: true
11
+ EnforcedStyle: double_quotes
12
+
13
+ # The default recommendation is 'loop do' rather than
14
+ # 'while true', but is measurably slower. Turn off.
15
+ Style/InfiniteLoop:
16
+ Enabled: false
17
+
18
+ # Override for rspec tests
19
+ Metrics/BlockLength:
20
+ Max: 100
21
+
22
+ Metrics/MethodLength:
23
+ Max: 20
24
+
25
+ Layout/LineLength:
26
+ Max: 120
27
+
28
+ require: rubocop-rake
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in xoroshiro.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+ gem 'rake-compiler'
10
+ gem 'rubocop-rake', require: false
11
+
12
+ # gem "", "~> "
13
+
14
+ gem 'rubocop', '~> 1.21'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Paul J Sanchez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Xoroshiro
2
+
3
+ This gem provides a port of the [Xoroshiro256\*\*](https://prng.di.unimi.it)
4
+ pseudo-random number generator (PRNG) to Ruby. Xoroshiro offers excellent
5
+ statistical performance and speed, matching or exceeding MT19937 (the PRNG
6
+ underpinning Ruby's built-in `Random` class) in both categories.
7
+
8
+ `Xoroshiro::rand` is intended as a drop-in replacement for `Kernel::rand`, providing
9
+ the same behavior given the same arguments. Enumeration is also provided via `:each`.
10
+
11
+ Timings on both MacOS and Windows show the gem's performance to be faster than
12
+ `Kernel::rand` for generating both floating point and integer values—substantially
13
+ so for the latter. You can confirm this for yourself by running the `xoroshiro_timings.rb`
14
+ script provided in the gem's `exe` folder.
15
+
16
+ ## Installation
17
+
18
+ Install the gem by executing:
19
+
20
+ $ gem install xoroshiro
21
+
22
+ ## License
23
+
24
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/extensiontask'
5
+ require 'rspec/core/rake_task'
6
+ require 'rubocop-rake'
7
+ require 'rubocop/rake_task'
8
+ require 'yard'
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ RuboCop::RakeTask.new do |task|
13
+ task.requires << 'rubocop-rake'
14
+ end
15
+
16
+ desc 'Build the extension'
17
+ task build: :compile
18
+
19
+ Rake::ExtensionTask.new('xoroshiro') do |ext|
20
+ ext.lib_dir = 'lib/xoroshiro'
21
+ end
22
+
23
+ YARD::Rake::YardocTask.new('doc') do |t|
24
+ t.options = ['--no-private'] # optional
25
+ end
26
+
27
+ task default: %i[clobber compile spec rubocop doc]
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/xoroshiro'
4
+ require 'benchmark/ips'
5
+
6
+ x = Xoroshiro::Random.new
7
+ rand_enum = Enumerator.new { |yielder| yielder << rand while true }.lazy
8
+ x_enum = x.each.lazy
9
+
10
+ puts 'Direct floating point calls...'
11
+ Benchmark.ips do |b|
12
+ b.report('rand float:') { 1000.times { rand } }
13
+ b.report('x rand float:') { 1000.times { x.rand } }
14
+ b.compare!
15
+ end
16
+
17
+ puts "\nInteger generation w/ high probability of rejection..."
18
+ Benchmark.ips do |b|
19
+ b.report(' rand P{rej} ~ 1/2:') { rand 0..0x80000000 }
20
+ b.report('x rand P{rej} ~ 1/2:') { x.rand 0..0x80000000 }
21
+ b.compare!
22
+ end
23
+
24
+ puts "\nInteger generation w/ zero probability of rejection..."
25
+ Benchmark.ips do |b|
26
+ b.report(' rand P{rej} = 0:') { rand 0..0x7fffffff }
27
+ b.report('x rand P{rej} = 0:') { x.rand 0..0x7fffffff }
28
+ b.compare!
29
+ end
30
+
31
+ puts "\nEnumerator.next..."
32
+ Benchmark.ips do |b|
33
+ b.report('rand next:') { 1000.times { rand_enum.next } }
34
+ b.report('x next:') { 1000.times { x_enum.next } }
35
+ b.compare!
36
+ end
37
+
38
+ puts "\nEnumerator.lazy.first in groups of 1000..."
39
+ Benchmark.ips do |b|
40
+ b.report('rand first 1k:') { rand_enum.first 1000 }
41
+ b.report('x first 1k:') { x_enum.first 1000 }
42
+ b.compare!
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ create_makefile('xoroshiro/xoroshiro')
@@ -0,0 +1,218 @@
1
+ #include "ruby.h"
2
+ #include "xoroshiro256.h"
3
+
4
+ static void seed_struct_free(seed_struct *self) {
5
+ if (self->seed != NULL) {
6
+ free(self->seed);
7
+ }
8
+ ruby_xfree(self);
9
+ }
10
+
11
+ static VALUE rb_seed_struct_alloc(VALUE klass) {
12
+ return Data_Wrap_Struct(klass, NULL, seed_struct_free, ruby_xmalloc(sizeof(seed_struct)));
13
+ }
14
+
15
+ /*
16
+ * Explicitly set the 256 bit state of the generator. Complement to +get_state+.
17
+ *
18
+ * *Arguments*::
19
+ * @param seeds [Array of 4 Integers]
20
+ *
21
+ * @return self
22
+ */
23
+ static VALUE set_state(VALUE self, VALUE seeds) {
24
+ Check_Type(seeds, T_ARRAY);
25
+ long seedsize = RARRAY_LEN(seeds);
26
+ if (seedsize != 4) {
27
+ rb_raise(rb_eTypeError, "set_state: Four integer seed values required");
28
+ }
29
+
30
+ seed_struct* seed_values;
31
+ Data_Get_Struct(self, seed_struct, seed_values);
32
+
33
+ seed_values->size = (uint32_t) seedsize;
34
+ seed_values->seed = (uint64_t *) malloc(seed_values->size * sizeof(uint64_t));
35
+ for (uint32_t i = 0; i < seedsize; ++i) {
36
+ seed_values->seed[i] = NUM2ULL(rb_ary_entry(seeds, i));
37
+ }
38
+ return self;
39
+ }
40
+
41
+ /*
42
+ * Retrieve the current 256 bit state of the generator. Complement to +set_state+.
43
+ *
44
+ * @return an array of four integers.
45
+ */
46
+ static VALUE get_state(VALUE self) {
47
+ seed_struct* seed_values;
48
+ Data_Get_Struct(self, seed_struct, seed_values);
49
+ VALUE state = rb_ary_new();
50
+ for (uint32_t i = 0; i < seed_values->size; ++i) {
51
+ rb_ary_push(state, ULL2NUM(seed_values->seed[i]));
52
+ }
53
+ return state;
54
+ }
55
+
56
+ /*
57
+ * Update the state to the equivalent of 2^128 calls to +rand()+.
58
+ * This can be used to generate 2^128 non-overlapping subsequences
59
+ * for parallel computations.
60
+ */
61
+ static VALUE do_jump(VALUE self) {
62
+ seed_struct* seed_values;
63
+ Data_Get_Struct(self, seed_struct, seed_values);
64
+ jump(seed_values);
65
+ return self;
66
+ }
67
+
68
+ /*
69
+ * Update the state to the equivalent of 2^192 calls to +rand()+.
70
+ * This can be used to generate 2^64 non-overlapping subsequences
71
+ * for parallel computations.
72
+ */
73
+ static VALUE do_long_jump(VALUE self) {
74
+ seed_struct* seed_values;
75
+ Data_Get_Struct(self, seed_struct, seed_values);
76
+ long_jump(seed_values);
77
+ return self;
78
+ }
79
+
80
+ /*
81
+ * Generate a random value as specified by +args+.
82
+ *
83
+ * *Arguments*::
84
+ * @param *args* [+nil+, +Range+, +Numeric+] specify the range and
85
+ * type of the generated value. (default: +nil+)
86
+ * - +nil+ -> generate a Uniform(0,1) +Float+ value
87
+ * - +Range+ -> generate a uniformly distributed value within the range
88
+ * of type:
89
+ * - if both bounds are +Integer+ -> +Integer+
90
+ * - otherwise -> +Float+
91
+ * - +Numeric+ *value* -> evaluated as +Range+ (0..*value*)
92
+ *
93
+ * @return a single value consistent with the specified range and type.
94
+ */
95
+ static VALUE rand_method(int argc, VALUE* argv, VALUE self) {
96
+ seed_struct* seed_values;
97
+ Data_Get_Struct(self, seed_struct, seed_values);
98
+
99
+ VALUE argument;
100
+ rb_scan_args(argc, argv, "01", &argument);
101
+
102
+ VALUE outcome = Qnil;
103
+
104
+ if (rb_obj_is_kind_of(argument, rb_cRange)) {
105
+ VALUE rb_beg;
106
+ VALUE rb_end;
107
+ int excl;
108
+ if (!rb_range_values(argument, &rb_beg, &rb_end, &excl)) return Qnil;
109
+ if (NIL_P(rb_beg) || NIL_P(rb_end)) return Qnil;
110
+ if (rb_obj_is_kind_of(rb_beg, rb_cNumeric) && rb_obj_is_kind_of(rb_end, rb_cNumeric)) {
111
+ if (RB_FLOAT_TYPE_P(rb_beg) || RB_FLOAT_TYPE_P(rb_end)) {
112
+ double begin = NUM2DBL(rb_beg);
113
+ double drange = NUM2DBL(rb_end) - begin;
114
+ outcome = DBL2NUM(begin + next_double(seed_values) * drange);
115
+ } else {
116
+ int64_t begin = NUM2LL(rb_beg);
117
+ int64_t end = NUM2LL(rb_end);
118
+ int64_t irange = end - begin;
119
+ if (!excl) {
120
+ irange += 1;
121
+ }
122
+ if (irange >= 0) outcome = LL2NUM(begin + nearlydivisionless(irange, seed_values));
123
+ }
124
+ }
125
+ return outcome;
126
+ } else {
127
+ switch (TYPE(argument)) {
128
+ case T_NIL:
129
+ outcome = DBL2NUM(next_double(seed_values));
130
+ break;
131
+ case T_FLOAT:
132
+ outcome = DBL2NUM(next_double(seed_values) * NUM2DBL(argument));
133
+ break;
134
+ case T_FIXNUM: {
135
+ int64_t upper_lim = NUM2LL(argument);
136
+ if (upper_lim > 0) outcome = ULL2NUM(nearlydivisionless(upper_lim, seed_values));
137
+ break;
138
+ }
139
+ case T_BIGNUM: {
140
+ uint64_t upper_lim = NUM2ULL(argument);
141
+ outcome = ULL2NUM(nearlydivisionless(upper_lim, seed_values));
142
+ break;
143
+ }
144
+ }
145
+ return outcome;
146
+ }
147
+ }
148
+
149
+ static VALUE each_u(VALUE self) {
150
+ if (!rb_block_given_p()) {
151
+ return rb_enumeratorize_with_size(
152
+ self, ID2SYM(rb_intern("each_u")), 0, 0, 0
153
+ );
154
+ }
155
+ seed_struct* seed_values;
156
+ Data_Get_Struct(self, seed_struct, seed_values);
157
+ for(;;) {
158
+ rb_yield(DBL2NUM(next_double(seed_values)));
159
+ }
160
+ return Qnil;
161
+ }
162
+
163
+ static VALUE each_u_range(VALUE self) {
164
+ if (!rb_block_given_p()) {
165
+ return rb_enumeratorize_with_size(
166
+ self, ID2SYM(rb_intern("each_u_range")), 0, 0, 0
167
+ );
168
+ }
169
+ seed_struct* seed_values;
170
+ Data_Get_Struct(self, seed_struct, seed_values);
171
+
172
+ VALUE obj_range = rb_ivar_get(self, rb_intern("@range"));
173
+ VALUE rb_beg;
174
+ VALUE rb_end;
175
+ int excl;
176
+ if (!rb_range_values(obj_range, &rb_beg, &rb_end, &excl)) return Qnil;
177
+ if (NIL_P(rb_beg) || NIL_P(rb_end)) return Qnil;
178
+ if (rb_obj_is_kind_of(rb_beg, rb_cNumeric) && rb_obj_is_kind_of(rb_end, rb_cNumeric)) {
179
+ if (RB_FLOAT_TYPE_P(rb_beg) || RB_FLOAT_TYPE_P(rb_end)) {
180
+ double begin = NUM2DBL(rb_beg);
181
+ double range = NUM2DBL(rb_end) - begin;
182
+ for(;;) {
183
+ rb_yield(DBL2NUM(begin + next_double(seed_values) * range));
184
+ }
185
+ } else {
186
+ int64_t begin = NUM2LL(rb_beg);
187
+ int64_t end = NUM2LL(rb_end);
188
+ int64_t range = end - begin;
189
+ if (!excl) {
190
+ range += 1;
191
+ }
192
+ if (range >= 0) {
193
+ for(;;) {
194
+ rb_yield(LL2NUM(begin + nearlydivisionless(range, seed_values)));
195
+ }
196
+ }
197
+ }
198
+ }
199
+ return Qnil;
200
+ }
201
+
202
+ VALUE rb_mXoroshiro = Qnil;
203
+ VALUE rb_cXgenerator = Qnil;
204
+ VALUE rb_cXrandom = Qnil;
205
+
206
+ void Init_xoroshiro(void) {
207
+ rb_mXoroshiro = rb_define_module("Xoroshiro");
208
+ rb_cXrandom = rb_define_class_under(rb_mXoroshiro, "Random", rb_cObject);
209
+
210
+ rb_define_alloc_func(rb_cXrandom, rb_seed_struct_alloc);
211
+ rb_define_method(rb_cXrandom, "set_state", set_state, 1);
212
+ rb_define_method(rb_cXrandom, "get_state", get_state, 0);
213
+ rb_define_private_method(rb_cXrandom, "each_u", each_u, 0);
214
+ rb_define_private_method(rb_cXrandom, "each_u_range", each_u_range, 0);
215
+ rb_define_method(rb_cXrandom, "rand", rand_method, -1);
216
+ rb_define_method(rb_cXrandom, "jump", do_jump, 0);
217
+ rb_define_method(rb_cXrandom, "long_jump", do_long_jump, 0);
218
+ }
@@ -0,0 +1,147 @@
1
+ #include <stdint.h>
2
+ #include "xoroshiro256.h"
3
+
4
+ static const double SCALE_VALUE = 0x1.0p-53;
5
+
6
+ double next_double(seed_struct* seed_values) {
7
+ return (next(seed_values) >> 11) * SCALE_VALUE;
8
+ }
9
+
10
+ /*
11
+ Fast nearly divisionless method for generating unbiased uniforms in any range.
12
+ Sourced from the appendix of:
13
+
14
+ Daniel Lemire. 2019. Fast Random Integer Generation in an Interval.
15
+ ACM Trans. Model. Comput. Simul. 29, 1, Article 3 (February 2019),
16
+ 12 pages. DOI:https://doi.org/10.1145/3230636
17
+ */
18
+ uint64_t nearlydivisionless(uint64_t bound, seed_struct* seed_values) {
19
+ uint64_t r_val = next(seed_values);
20
+ __uint128_t result = (__uint128_t) r_val * (__uint128_t) bound;
21
+ uint64_t l = (uint64_t) result;
22
+ if (l < bound) {
23
+ // uint64_t t = -bound % bound;
24
+ uint64_t t = (0xFFFFFFFFFFFFFFFFull - bound + 1) % bound;
25
+ while (l < t) {
26
+ r_val = next(seed_values);
27
+ result = (__uint128_t) r_val * (__uint128_t) bound;
28
+ l = (uint64_t) result;
29
+ }
30
+ }
31
+ return result >> 64;
32
+ }
33
+
34
+ /* XOROSHIRO */
35
+
36
+ /*
37
+ Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)
38
+
39
+ To the extent possible under law, the author has dedicated all copyright
40
+ and related and neighboring rights to this software to the public domain
41
+ worldwide. This software is distributed without any warranty.
42
+
43
+ See <http://creativecommons.org/publicdomain/zero/1.0/>.
44
+ */
45
+
46
+ /*
47
+ This is xoshiro256** 1.0, one of our all-purpose, rock-solid
48
+ generators. It has excellent (sub-ns) speed, a state (256 bits) that is
49
+ large enough for any parallel application, and it passes all tests we
50
+ are aware of.
51
+
52
+ For generating just floating-point numbers, xoshiro256+ is even faster.
53
+
54
+ The state must be seeded so that it is not everywhere zero. If you have
55
+ a 64-bit seed, we suggest to seed a splitmix64 generator and use its
56
+ output to fill seed_values->seed.
57
+ */
58
+
59
+ /*
60
+ Modified for Ruby extension use in 2023 by Paul J Sanchez (pjs@alum.mit.edu)
61
+ */
62
+
63
+ static inline uint64_t rotl(const uint64_t x, int k) {
64
+ return (x << k) | (x >> (64 - k));
65
+ }
66
+
67
+ uint64_t next(seed_struct *seed_values) {
68
+ const uint64_t result = rotl(seed_values->seed[1] * 5, 7) * 9;
69
+
70
+ const uint64_t t = seed_values->seed[1] << 17;
71
+
72
+ seed_values->seed[2] ^= seed_values->seed[0];
73
+ seed_values->seed[3] ^= seed_values->seed[1];
74
+ seed_values->seed[1] ^= seed_values->seed[2];
75
+ seed_values->seed[0] ^= seed_values->seed[3];
76
+
77
+ seed_values->seed[2] ^= t;
78
+
79
+ seed_values->seed[3] = rotl(seed_values->seed[3], 45);
80
+
81
+ return result;
82
+ }
83
+
84
+
85
+ /*
86
+ This is the jump function for the generator. It is equivalent
87
+ to 2^128 calls to next(); it can be used to generate 2^128
88
+ non-overlapping subsequences for parallel computations.
89
+ */
90
+
91
+ void jump(seed_struct *seed_values) {
92
+ static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c };
93
+
94
+ uint64_t s0 = 0;
95
+ uint64_t s1 = 0;
96
+ uint64_t s2 = 0;
97
+ uint64_t s3 = 0;
98
+ for(uint32_t i = 0; i < sizeof JUMP / sizeof *JUMP; i++)
99
+ for(int b = 0; b < 64; b++) {
100
+ if (JUMP[i] & UINT64_C(1) << b) {
101
+ s0 ^= seed_values->seed[0];
102
+ s1 ^= seed_values->seed[1];
103
+ s2 ^= seed_values->seed[2];
104
+ s3 ^= seed_values->seed[3];
105
+ }
106
+ next(seed_values);
107
+ }
108
+
109
+ seed_values->seed[0] = s0;
110
+ seed_values->seed[1] = s1;
111
+ seed_values->seed[2] = s2;
112
+ seed_values->seed[3] = s3;
113
+ }
114
+
115
+
116
+
117
+ /*
118
+ This is the long-jump function for the generator. It is equivalent to
119
+ 2^192 calls to next(); it can be used to generate 2^64 starting points,
120
+ from each of which jump() will generate 2^64 non-overlapping
121
+ subsequences for parallel distributed computations.
122
+ */
123
+
124
+ void long_jump(seed_struct *seed_values) {
125
+ static const uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 };
126
+
127
+ uint64_t s0 = 0;
128
+ uint64_t s1 = 0;
129
+ uint64_t s2 = 0;
130
+ uint64_t s3 = 0;
131
+ for(uint32_t i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++)
132
+ for(int b = 0; b < 64; b++) {
133
+ if (LONG_JUMP[i] & UINT64_C(1) << b) {
134
+ s0 ^= seed_values->seed[0];
135
+ s1 ^= seed_values->seed[1];
136
+ s2 ^= seed_values->seed[2];
137
+ s3 ^= seed_values->seed[3];
138
+ }
139
+ next(seed_values);
140
+ }
141
+
142
+ seed_values->seed[0] = s0;
143
+ seed_values->seed[1] = s1;
144
+ seed_values->seed[2] = s2;
145
+ seed_values->seed[3] = s3;
146
+ }
147
+
@@ -0,0 +1,15 @@
1
+ #ifndef XOROSHIRO256_H
2
+ #define XOROSHIRO256_H 1
3
+
4
+ typedef struct {
5
+ uint64_t *seed;
6
+ uint32_t size;
7
+ } seed_struct;
8
+
9
+ uint64_t nearlydivisionless(uint64_t bound, seed_struct *seed_values);
10
+ double next_double(seed_struct *seed_values);
11
+ uint64_t next(seed_struct *seed_values);
12
+ void jump(seed_struct *seed_values);
13
+ void long_jump(seed_struct *seed_values);
14
+
15
+ #endif /* XOROSHIRO256_H */
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xoroshiro
4
+ VERSION = '0.2.0'
5
+ end
data/lib/xoroshiro.rb ADDED
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'xoroshiro/version'
4
+ require_relative 'xoroshiro/xoroshiro'
5
+
6
+ require 'openssl'
7
+ require 'securerandom'
8
+
9
+ ##
10
+ # The +Xoroshiro256\*\*+ pseudo-random number generator implemented as a Ruby
11
+ # C extension. For more information about the Xoroshiro family of generators
12
+ # see https://prng.di.unimi.it
13
+ #
14
+ module Xoroshiro
15
+ class Error < StandardError; end
16
+
17
+ private_constant
18
+ BITMASK64 = (1 << 64) - 1
19
+
20
+ # @private
21
+ def self.gen_seed_state(seed)
22
+ if seed.nil?
23
+ # seed values from entropy via SecureRandom
24
+ Array.new(4) { SecureRandom.hex(8).to_i(16) }
25
+ else
26
+ # seed values by sha26 digest of provided value
27
+ digest = OpenSSL::Digest.hexdigest('sha256', seed.to_s).to_i(16)
28
+ Array.new(4) { |i| (digest >> (i * 64)) & BITMASK64 }
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Provides pseudo-random variate generation based on +Xoroshiro256\*\*+.
34
+ #
35
+ # Quoting the original Xoroshiro creators:
36
+ # >>>
37
+ # ...xoshiro256** 1.0 [is] one of our all-purpose, rock-solid
38
+ # generators. It has excellent (sub-ns) speed, a state (256 bits) that is
39
+ # large enough for any parallel application, and it passes all tests we
40
+ # are aware of.
41
+ #
42
+ # This generator outperforms MT19937, the underlying engine for
43
+ # +Kernel::rand+, in both speed and statistical performance.
44
+ #
45
+ class Random
46
+ ##
47
+ # *Arguments*::
48
+ # @param *range* [+nil+, +Range+, +Numeric+] specify the range and
49
+ # type of the values produced by +:each+. (default: +nil+)
50
+ # - +nil+ -> generate Uniform(0,1) +Float+ values
51
+ # - +Range+ -> generate uniformly distributed values within the range
52
+ # of type:
53
+ # - if both bounds are +Integer+ -> +Integer+
54
+ # - otherwise -> +Float+
55
+ # - +Numeric+ *value* -> evaluated as +Range+ 0..*value*
56
+ # @param *seed:* [+nil+, +Numeric+] initialize the generator state for
57
+ # reproducibility. (default: +nil+)
58
+ # - +nil+ -> use system entropy via +SecureRandom+ to generate
59
+ # 256 bits of initial state.
60
+ # - +Numeric+ *value* -> calculate SHA256 hash of *value* to use
61
+ # as the initial state.
62
+ #
63
+ def initialize(range = nil, seed: nil)
64
+ set_state(Xoroshiro.gen_seed_state(seed))
65
+ gen_method = method(:each_u_range)
66
+ case range
67
+ when nil
68
+ gen_method = method(:each_u)
69
+ when Numeric
70
+ @range = 0..range
71
+ when Range
72
+ @range = range
73
+ else
74
+ warn "Invalid range argument: #{range}"
75
+ gen_method = method(:each_u)
76
+ end
77
+ @generator = gen_method.call
78
+ end
79
+
80
+ ##
81
+ # Yields random values determined by the *range* argument of +initialize+.
82
+ # @return an +Enumerator+.
83
+ def each
84
+ @generator
85
+ end
86
+ end
87
+
88
+ private_constant
89
+ CLASS_RAND = Xoroshiro::Random.new
90
+
91
+ ##
92
+ # Module-level +rand+ method which can be used as a drop-in replacement
93
+ # for +Kernel::rand+.
94
+ #
95
+ # *Arguments*::
96
+ # @param *range* [+nil+, +Range+, +Numeric+] as described in the
97
+ # +Xoroshiro::Random.rand+ instance method.
98
+ #
99
+ # @return a random value corresponding to the +range+ specification.
100
+ #
101
+ def self.rand(range = nil)
102
+ CLASS_RAND.rand(range)
103
+ end
104
+ end
data/sig/xoroshiro.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Xoroshiro
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/xoroshiro.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/xoroshiro/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'xoroshiro'
7
+ spec.version = Xoroshiro::VERSION
8
+ spec.authors = ['Paul J Sanchez']
9
+ spec.email = ['pjs@alum.mit.edu']
10
+
11
+ spec.summary = 'Port of Xoroshiro256** PRNG to a ruby C-extension.'
12
+ # spec.description = "TODO: Write a longer description or delete this line."
13
+ spec.homepage = 'https://bitbucket.org/paul_j_sanchez/xoroshiro_ruby_gem'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.0.0'
16
+
17
+ spec.metadata = {
18
+ 'rubygems_mfa_required' => 'true',
19
+ 'homepage_uri' => spec.homepage,
20
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/xoroshiro'
21
+ }
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = 'exe'
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ['lib']
33
+ spec.extensions = ['ext/xoroshiro/extconf.rb']
34
+
35
+ # Uncomment to register a new dependency of your gem
36
+ # spec.add_dependency "example-gem", "~> 1.0"
37
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xoroshiro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul J Sanchez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-05-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - pjs@alum.mit.edu
16
+ executables:
17
+ - xoroshiro_timings.rb
18
+ extensions:
19
+ - ext/xoroshiro/extconf.rb
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".rspec"
23
+ - ".rubocop.yml"
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - exe/xoroshiro_timings.rb
29
+ - ext/xoroshiro/extconf.rb
30
+ - ext/xoroshiro/xoroshiro.c
31
+ - ext/xoroshiro/xoroshiro256.c
32
+ - ext/xoroshiro/xoroshiro256.h
33
+ - lib/xoroshiro.rb
34
+ - lib/xoroshiro/version.rb
35
+ - sig/xoroshiro.rbs
36
+ - xoroshiro.gemspec
37
+ homepage: https://bitbucket.org/paul_j_sanchez/xoroshiro_ruby_gem
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ rubygems_mfa_required: 'true'
42
+ homepage_uri: https://bitbucket.org/paul_j_sanchez/xoroshiro_ruby_gem
43
+ documentation_uri: https://www.rubydoc.info/gems/xoroshiro
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 3.0.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.4.13
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Port of Xoroshiro256** PRNG to a ruby C-extension.
63
+ test_files: []