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 +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: []
|