xoroshiro 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +28 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +24 -0
- data/Rakefile +27 -0
- data/exe/xoroshiro_timings.rb +43 -0
- data/ext/xoroshiro/extconf.rb +5 -0
- data/ext/xoroshiro/xoroshiro.c +218 -0
- data/ext/xoroshiro/xoroshiro256.c +147 -0
- data/ext/xoroshiro/xoroshiro256.h +15 -0
- data/lib/xoroshiro/version.rb +5 -0
- data/lib/xoroshiro.rb +104 -0
- data/sig/xoroshiro.rbs +4 -0
- data/xoroshiro.gemspec +37 -0
- metadata +63 -0
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,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 */
|
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
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: []
|