xoroshiro 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []