├── .gitignore
├── app.hxml
├── hxml
├── app.cpp.hxml
├── test.cpp.hxml
├── app.neko.hxml
├── test.hl.hxml
├── test.js.hxml
└── test.neko.hxml
├── test.hxml
├── doc.hxml
├── test.html
├── test
└── seedyrng
│ ├── tests
│ ├── TestGaloisLFSR32.hx
│ ├── TestXorshift64Plus.hx
│ ├── TestXorshift128Plus.hx
│ ├── TestSeedy.hx
│ └── TestRandom.hx
│ ├── TestAll.hx
│ ├── SpeedTest.hx
│ └── GeneratorTester.hx
├── .travis.yml
├── haxelib.json
├── script
└── zip_release.sh
├── CHANGELOG.md
├── src
└── seedyrng
│ ├── GeneratorInterface.hx
│ ├── GaloisLFSR32.hx
│ ├── Xorshift64Plus.hx
│ ├── Xorshift128Plus.hx
│ ├── Seedy.hx
│ └── Random.hx
├── LICENSE.txt
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Output files
2 | out/
3 |
--------------------------------------------------------------------------------
/app.hxml:
--------------------------------------------------------------------------------
1 | -cp src/
2 | -main seedyrng.Seedy
3 |
--------------------------------------------------------------------------------
/hxml/app.cpp.hxml:
--------------------------------------------------------------------------------
1 | app.hxml
2 | -cpp out/cpp/
3 |
--------------------------------------------------------------------------------
/hxml/test.cpp.hxml:
--------------------------------------------------------------------------------
1 | test.hxml
2 | -cpp out/cpp/
3 |
--------------------------------------------------------------------------------
/hxml/app.neko.hxml:
--------------------------------------------------------------------------------
1 | app.hxml
2 | -neko out/neko/app.n
3 |
--------------------------------------------------------------------------------
/hxml/test.hl.hxml:
--------------------------------------------------------------------------------
1 | test.hxml
2 | -hl out/hl/test.hl
3 |
--------------------------------------------------------------------------------
/hxml/test.js.hxml:
--------------------------------------------------------------------------------
1 | test.hxml
2 | -js out/js/test.js
3 |
--------------------------------------------------------------------------------
/hxml/test.neko.hxml:
--------------------------------------------------------------------------------
1 | test.hxml
2 | -neko out/neko/test.n
3 |
--------------------------------------------------------------------------------
/test.hxml:
--------------------------------------------------------------------------------
1 | -cp src/
2 | -cp test/
3 | -lib utest
4 | -debug
5 | -main seedyrng.TestAll
6 |
--------------------------------------------------------------------------------
/doc.hxml:
--------------------------------------------------------------------------------
1 | -cp src/
2 | -xml out/xml/doc.xml
3 | -D doc-gen
4 | --macro include('seedyrng')
5 | --no-output
6 | -neko out/dummy.n
7 |
8 | --next
9 | -cmd haxelib run dox -i out/xml/ -o out/docs/ --include "seedyrng" --title "SeedyRNG API Documentation"
10 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/test/seedyrng/tests/TestGaloisLFSR32.hx:
--------------------------------------------------------------------------------
1 | package seedyrng.tests;
2 |
3 | import utest.Test;
4 | import utest.Assert;
5 |
6 |
7 | class TestGaloisLFSR32 extends GeneratorTester {
8 | public function new () {
9 | super(GaloisLFSR32.new);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/seedyrng/tests/TestXorshift64Plus.hx:
--------------------------------------------------------------------------------
1 | package seedyrng.tests;
2 |
3 | import utest.Test;
4 | import utest.Assert;
5 |
6 |
7 | class TestXorshift64Plus extends GeneratorTester {
8 | public function new () {
9 | super(Xorshift64Plus.new);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/seedyrng/tests/TestXorshift128Plus.hx:
--------------------------------------------------------------------------------
1 | package seedyrng.tests;
2 |
3 | import utest.Test;
4 | import utest.Assert;
5 |
6 |
7 | class TestXorshift128Plus extends GeneratorTester {
8 | public function new () {
9 | super(Xorshift128Plus.new);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: haxe
2 |
3 | haxe:
4 | - stable
5 |
6 | install:
7 | - yes | haxelib install test.hxml
8 | - yes | haxelib install hxcpp
9 |
10 | script:
11 | - haxe hxml/test.neko.hxml
12 | - haxe hxml/test.cpp.hxml
13 | - haxe hxml/test.js.hxml
14 | - neko out/neko/test.n
15 | - out/cpp/TestAll-debug
16 |
--------------------------------------------------------------------------------
/haxelib.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "seedyrng",
3 | "url" : "https://github.com/chfoo/seedyrng/",
4 | "license": "MIT",
5 | "tags": ["cross", "rng", "random"],
6 | "description": "Pseudorandom number generator library",
7 | "version": "1.1.0",
8 | "classPath": "src/",
9 | "releasenote": "Added xorshift64+ generator (see changelog)",
10 | "contributors": ["chfoo"],
11 | "dependencies": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/script/zip_release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/dash -e
2 |
3 | cd ../
4 |
5 | echo Working directory: $PWD
6 | DIR=$PWD
7 |
8 | NAME=seedyrng
9 | VERSION=$(python3 -c "import json
10 | with open('haxelib.json') as file:
11 | print(json.load(file)['version'])
12 | ")
13 | TIMESTAMP=$(date --utc +%Y%m%d-%H%M%S)
14 |
15 | mkdir -p out/release/
16 | git archive HEAD -o out/release/$NAME-$VERSION-$TIMESTAMP.zip
17 |
18 | echo Done.
19 |
--------------------------------------------------------------------------------
/test/seedyrng/TestAll.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import utest.Runner;
4 | import utest.ui.Report;
5 |
6 | class TestAll {
7 | public static function main() {
8 | #if sys
9 | if (Sys.args().indexOf("--speed-test") >= 0) {
10 | SpeedTest.main();
11 | }
12 | #end
13 |
14 | var runner = new Runner();
15 | runner.addCases(seedyrng.tests);
16 |
17 | Report.create(runner);
18 | runner.run();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change log
2 | ==========
3 |
4 | 1.1.0 (2020-04-06)
5 | ------------------
6 |
7 | * Added `Xorshift64Plus`. The default `Xorshift128Plus` uses `haxe.Int64`
8 | in the implementation which can be very slow on targets without 64-bit
9 | integers.
10 |
11 | 1.0.0 (2019-03-23)
12 | ------------------
13 |
14 | * Changed `randomSystemInt()` implementation to remove target specific code.
15 | Use the trandom library for high quality random numbers.
16 | * Bumped version to 1.0 to indicate API is stable.
17 |
18 | 0.1.0 (2018-10-19)
19 | ------------------
20 |
21 | * First release
22 |
--------------------------------------------------------------------------------
/test/seedyrng/SpeedTest.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import haxe.Timer;
5 |
6 | class SpeedTest {
7 | public static function main() {
8 | final seed = Int64.make(0, 123);
9 | final xorshift128pRandom = new Random(seed, new Xorshift128Plus());
10 | final xorshift64pRandom = new Random(seed, new Xorshift64Plus());
11 | final galoisLFSR32Random = new Random(seed, new GaloisLFSR32());
12 | final count = 1000000;
13 |
14 | Timer.measure(() -> {
15 | for (dummy in 0...count) {
16 | xorshift128pRandom.random();
17 | }
18 | });
19 |
20 | Timer.measure(() -> {
21 | for (dummy in 0...count) {
22 | xorshift64pRandom.random();
23 | }
24 | });
25 |
26 | Timer.measure(() -> {
27 | for (dummy in 0...count) {
28 | galoisLFSR32Random.random();
29 | }
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/seedyrng/GeneratorInterface.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import haxe.io.Bytes;
5 |
6 |
7 | /**
8 | Interface for pseudorandom number generators.
9 | **/
10 | interface GeneratorInterface {
11 | /**
12 | Initial starting state.
13 |
14 | Setting this value will reset the state.
15 | **/
16 |
17 | public var seed(get, set):Int64;
18 |
19 | /**
20 | Current generator state.
21 |
22 | The state can be saved and restored to allow resuming a generator.
23 | **/
24 | public var state(get, set):Bytes;
25 |
26 | /**
27 | Whether `nextInt` returns an integer with a uniform distribution of
28 | bits.
29 |
30 | When `false` for example, the generator may output an integer that is
31 | never zero or the most significant bit is zero.
32 | **/
33 | public var usesAllBits(get, never):Bool;
34 |
35 | /**
36 | Advances the generator and returns an integer.
37 |
38 | The returned value is subject to notes in `usesAllBits`.
39 | **/
40 | public function nextInt():Int;
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2020 Christopher Foo
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/seedyrng/tests/TestSeedy.hx:
--------------------------------------------------------------------------------
1 | package seedyrng.tests;
2 |
3 | import utest.Assert;
4 | import utest.Test;
5 |
6 |
7 | class TestSeedy extends Test {
8 | public function testRandom() {
9 | var num = Seedy.random();
10 | Assert.isTrue(num >= 0.0);
11 | Assert.isTrue(num < 1.0);
12 | }
13 |
14 | public function testRandomInt() {
15 | var num = Seedy.randomInt(-100, 500);
16 |
17 | Assert.isTrue(num >= -100);
18 | Assert.isTrue(num <= 500);
19 | }
20 |
21 | public function testUniform() {
22 | var num = Seedy.uniform(-100.8, 500.3);
23 |
24 | Assert.isTrue(num >= -100.8);
25 | Assert.isTrue(num < 500.3);
26 | }
27 |
28 | public function testChoice() {
29 | var array = ["a", "b", "c", "d", "e", "f"];
30 |
31 | var selected = Seedy.choice(array);
32 | Assert.notEquals(-1, array.indexOf(selected));
33 | }
34 |
35 | public function testShuffle() {
36 | var array = ["a", "b", "c", "d", "e", "f"];
37 | var shuffledArray = array.copy();
38 |
39 | Seedy.shuffle(shuffledArray);
40 | Assert.notEquals(array.toString(), shuffledArray.toString());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/seedyrng/GeneratorTester.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import utest.Assert;
5 | import utest.Test;
6 |
7 |
8 | class GeneratorTester extends Test {
9 | var generatorFactory:Void->GeneratorInterface;
10 |
11 | public function new(generatorFactory:Void->GeneratorInterface) {
12 | super();
13 | this.generatorFactory = generatorFactory;
14 | }
15 |
16 | public function testStateRestore() {
17 | var generator = generatorFactory();
18 | generator.nextInt();
19 |
20 | var state = generator.state;
21 | var values = [];
22 |
23 | for (index in 0...5) {
24 | values[index] = generator.nextInt();
25 | }
26 |
27 | generator.state = state;
28 |
29 | for (index in 0...5) {
30 | Assert.equals(values[index], generator.nextInt());
31 | }
32 | }
33 |
34 | public function testNext() {
35 | var generator = generatorFactory();
36 | var counter = new Map();
37 | var trials = 10;
38 |
39 | for (index in 0...trials) {
40 | var value = generator.nextInt();
41 |
42 | if (!counter.exists(value)) {
43 | counter.set(value, 1);
44 | } else {
45 | counter.set(value, counter.get(value) + 1);
46 | }
47 | }
48 |
49 | for (key in counter.keys()) {
50 | var count = counter.get(key);
51 |
52 | Assert.isTrue(count < 2, 'Value=$key Count=$count');
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/seedyrng/GaloisLFSR32.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import haxe.io.Bytes;
5 |
6 |
7 | /**
8 | Galois LFSR 32-bit generator.
9 |
10 | This is a very fast but low quality pseudorandom number generator. These
11 | kinds of LFSR generators are typically used in embedded devices with
12 | limited resources.
13 |
14 | In most cases, you do not want to use this generator.
15 | **/
16 | class GaloisLFSR32 implements GeneratorInterface {
17 | public var seed(get, set):Int64;
18 | public var state(get, set):Bytes;
19 | public var usesAllBits(get, never):Bool;
20 |
21 | // Implementation based on https://en.wikipedia.org/w/index.php?title=Linear-feedback_shift_register&oldid=863159640
22 | // Polynomial taps for 32 bit: 32, 22, 2, 1
23 | // 0b10000000001000000000000000000101
24 | static inline var TAPS:Int = 0x80200005;
25 |
26 | var _seed:Int64;
27 | var _state:Int;
28 |
29 | public function new() {
30 | this.seed = Int64.ofInt(1);
31 | }
32 |
33 | function get_usesAllBits():Bool {
34 | return false;
35 | }
36 |
37 | function get_seed():Int64 {
38 | return _seed;
39 | }
40 |
41 | function set_seed(value:Int64):Int64 {
42 | value = value != 0 ? value : 1; // must not be zero
43 | _seed = value;
44 | _state = _seed.high ^ _seed.low;
45 |
46 | return value;
47 | }
48 |
49 | function get_state():Bytes {
50 | var bytes = Bytes.alloc(12);
51 | bytes.setInt64(0, _seed);
52 | bytes.setInt32(8, _state);
53 |
54 | return bytes;
55 | }
56 |
57 | function set_state(value:Bytes):Bytes {
58 | if (value.length != 12) {
59 | throw 'Wrong state size ${value.length}';
60 | }
61 |
62 | _seed = value.getInt64(0);
63 | _state = value.getInt32(8);
64 |
65 | return value;
66 | }
67 |
68 | function stepNext() {
69 | var lsb = _state & 1;
70 | _state >>= 1;
71 |
72 | if (lsb != 0) {
73 | _state ^= TAPS;
74 | }
75 | }
76 |
77 | public function nextInt():Int {
78 | stepNext();
79 | return _state;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/seedyrng/Xorshift64Plus.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import haxe.io.Bytes;
5 |
6 | /**
7 | * xorshift64+ generator.
8 | *
9 | * This is a variant of the xorshift128+ generator that does not use haxe.Int64
10 | * in the implementation to avoid performance issues.
11 | * For details about xorshift, see `Xorshift128Plus`.
12 | */
13 | class Xorshift64Plus implements GeneratorInterface {
14 | public var seed(get, set):Int64;
15 | public var state(get, set):Bytes;
16 | public var usesAllBits(get, never):Bool;
17 |
18 | // Implementation based on
19 | // https://github.com/golang/go/issues/21806
20 | // https://go-review.googlesource.com/c/go/+/62530/
21 | static inline var PARAMETER_A = 17;
22 | static inline var PARAMETER_B = 7;
23 | static inline var PARAMETER_C = 16;
24 |
25 | var _seed:Int64;
26 | var _state0:Int;
27 | var _state1:Int;
28 |
29 | public function new() {
30 | this.seed = Int64.ofInt(1);
31 | }
32 |
33 | function get_usesAllBits():Bool {
34 | // false because weakness in the lower bits
35 | return false;
36 | }
37 |
38 | function get_seed():Int64 {
39 | return _seed;
40 | }
41 |
42 | function set_seed(value:Int64):Int64 {
43 | value = value != 0 ? value : 1; // must not be zero
44 | _seed = value;
45 |
46 | _state0 = value.high;
47 | _state1 = value.low;
48 |
49 | return value;
50 | }
51 |
52 | function get_state():Bytes {
53 | var bytes = Bytes.alloc(16);
54 |
55 | bytes.setInt64(0, _seed);
56 | bytes.setInt32(8, _state0);
57 | bytes.setInt32(12, _state1);
58 |
59 | return bytes;
60 | }
61 |
62 | function set_state(value:Bytes):Bytes {
63 | if (value.length != 16) {
64 | throw 'Wrong state size ${value.length}';
65 | }
66 |
67 | _seed = value.getInt64(0);
68 | _state0 = value.getInt32(8);
69 | _state1 = value.getInt32(12);
70 |
71 | return value;
72 | }
73 |
74 | public function nextInt():Int {
75 | var x = _state0;
76 | var y = _state1;
77 |
78 | _state0 = y;
79 | x ^= x << PARAMETER_A;
80 | _state1 = x ^ y ^ (x >> PARAMETER_B) ^ (y >> PARAMETER_C);
81 |
82 | return (_state1 + y) & 0xffffffff;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SeedyRNG
2 | ========
3 |
4 | SeedyRNG (Seedy) is a pseudorandom number generator library for Haxe.
5 |
6 | Seedy is intended for generating numbers for applications that require reproducible sequences. Such examples include video game world generation or unit tests.
7 |
8 | It provides a general featured interface such as producing an integer within a range or choosing an item from an array. It also allows you to use your own generator implementation if you desire.
9 |
10 | Seedy is deterministic or predictable which means it is *not* suitable for secure cryptographic purposes. (You may be interested in the [trandom library](https://lib.haxe.org/p/trandom) or a libsodium binding which provides an API to the OS cryptographic random number generator.)
11 |
12 | Quick start
13 | -----------
14 |
15 | Requires Haxe 3 or 4.
16 |
17 | Install it from Haxelib:
18 |
19 | haxelib install seedyrng
20 |
21 | (To install the latest from the git repository, use `haxelib git`.)
22 |
23 | If you simply need a random integer:
24 |
25 | ```haxe
26 | Seedy.randomInt(0, 10); // => Int
27 | ```
28 |
29 | If you need finer control, such as specifying the seed, use an instance of `Random`:
30 |
31 | ```haxe
32 | var random = new Random();
33 | random.setStringSeed("hello world!");
34 | random.randomInt(0, 10); // => Int
35 | ```
36 |
37 | By default, the generator is xorshift128+. It is a relatively new generator based on the xorshift family. It is comparable to the popular Mersenne Twister but it is faster and simpler.
38 |
39 | If you want to use another generator, you can specify it on the constructor:
40 |
41 | ```haxe
42 | var random = new Random(new GaloisLFSR32());
43 | // or
44 | var random = new Random(new Xorshift64Plus());
45 | ```
46 |
47 | The included `Xorshift128Plus` generator may be very slow on targets withouts 64-bit integers. For a generator that is fast on all targets, it is recommended to use `Xorshift64Plus` instead.
48 |
49 | For details on all the methods, see the [API documentation](https://chfoo.github.io/seedyrng/api/).
50 |
51 | Randomness testing
52 | ------------------
53 |
54 | If you desire, you can statistically test the generator using something like:
55 |
56 | haxe hxml/app.cpp.hxml && out/cpp/Seedy | dieharder -g 200 -a
57 |
58 | Alternatively, you can [inspect a visualization](https://unix.stackexchange.com/a/289670) of the output:
59 |
60 | export X=1000 Y=1000; haxe hxml/app.cpp.hxml && out/cpp/Seedy | head -c "$((3*X*Y))" | display -depth 8 -size "${X}x${Y}" RGB:-
61 |
62 | Contributing
63 | ------------
64 |
65 | Please file bug reports, features, or pull requests using the repo's GitHub Issues.
66 |
--------------------------------------------------------------------------------
/test/seedyrng/tests/TestRandom.hx:
--------------------------------------------------------------------------------
1 | package seedyrng.tests;
2 |
3 | import haxe.Int64;
4 | import utest.Assert;
5 | import utest.Test;
6 |
7 |
8 | class TestRandom extends Test {
9 | public function testRandomSystemInt() {
10 | Assert.notNull(Random.randomSystemInt());
11 | }
12 |
13 | public function testNextInt() {
14 | var random = new Random(Int64.ofInt(1));
15 | var num = random.nextInt();
16 |
17 | Assert.notNull(num);
18 | }
19 |
20 | public function testNextFullInt() {
21 | var random = new Random(Int64.ofInt(1));
22 | var num = random.nextFullInt();
23 |
24 | Assert.notEquals(0, num);
25 | }
26 |
27 | public function testSetStringSeed() {
28 | var random1 = new Random();
29 | var random2 = new Random();
30 | var random3 = new Random();
31 |
32 | random1.setStringSeed("hello world!");
33 | random2.setStringSeed("hello world!");
34 | random3.setStringSeed("abc");
35 |
36 | var num1 = random1.nextInt();
37 | var num2 = random2.nextInt();
38 | var num3 = random3.nextInt();
39 |
40 | Assert.equals(num1, num2);
41 | Assert.notEquals(num1, num3);
42 | }
43 |
44 | public function testRandom() {
45 | var random = new Random(Int64.ofInt(1));
46 |
47 | for (index in 0...10) {
48 | var num = random.random();
49 |
50 | Assert.isTrue(num >= 0.0);
51 | Assert.isTrue(num < 1.0);
52 | }
53 | }
54 |
55 | public function testRandomInt() {
56 | var random = new Random(Int64.ofInt(1));
57 |
58 | for (index in 0...10) {
59 | var num = random.randomInt(-100, 500);
60 |
61 | Assert.isTrue(num >= -100);
62 | Assert.isTrue(num <= 500);
63 | }
64 | }
65 |
66 | public function testUniform() {
67 | var random = new Random(Int64.ofInt(1));
68 |
69 | for (index in 0...10) {
70 | var num = random.uniform(-100.8, 500.3);
71 |
72 | Assert.isTrue(num >= -100.8);
73 | Assert.isTrue(num < 500.3);
74 | }
75 | }
76 |
77 | public function testChoice() {
78 | var array = ["a", "b", "c", "d", "e", "f"];
79 | var random = new Random(Int64.ofInt(1));
80 |
81 | var selected = random.choice(array);
82 |
83 | Assert.notEquals(-1, array.indexOf(selected));
84 | }
85 |
86 | public function testShuffle() {
87 | var array = ["a", "b", "c", "d", "e", "f"];
88 | var shuffledArray = array.copy();
89 | var random = new Random(Int64.ofInt(1));
90 |
91 | random.shuffle(shuffledArray);
92 |
93 | Assert.notEquals(array.toString(), shuffledArray.toString());
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/seedyrng/Xorshift128Plus.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.Int64;
4 | import haxe.io.Bytes;
5 |
6 |
7 | /**
8 | xorshift128+ generator.
9 |
10 | This is a very fast and high quality pseudorandom number generator.
11 | It is a variant of the xorshift algorithm that is closely related to LFSRs.
12 |
13 | On targets without 64-bit integers, the usage of haxe.Int64 may be very
14 | slow. If this is a concern, use `Xorshift64Plus`.
15 | **/
16 | class Xorshift128Plus implements GeneratorInterface {
17 | public var seed(get, set):Int64;
18 | public var state(get, set):Bytes;
19 | public var usesAllBits(get, never):Bool;
20 |
21 | // Implementation based on https://en.wikipedia.org/w/index.php?title=Xorshift&oldid=856827334
22 | static inline var PARAMETER_A = 23;
23 | static inline var PARAMETER_B = 17;
24 | static inline var PARAMETER_C = 26;
25 |
26 | // Randomly generated value
27 | static var SEED_1 = Int64.make(0x3239D498, 0x28D8D419);
28 |
29 | var _seed:Int64;
30 | var _state0:Int64;
31 | var _state1:Int64;
32 | var _current:Int64;
33 | var _currentAvailable = false;
34 |
35 | public function new() {
36 | this.seed = Int64.ofInt(1);
37 | }
38 |
39 | function get_usesAllBits():Bool {
40 | // false because weakness in the lower bits
41 | return false;
42 | }
43 |
44 | function get_seed():Int64 {
45 | return _seed;
46 | }
47 |
48 | function set_seed(value:Int64):Int64 {
49 | value = value != 0 ? value : 1; // must not be zero
50 | _seed = value;
51 |
52 | // Our API only supports 64 bit seed, so there is a hard-coded
53 | // value here
54 | _state0 = value;
55 | _state1 = SEED_1;
56 | _currentAvailable = false;
57 |
58 | return value;
59 | }
60 |
61 | function get_state():Bytes {
62 | var bytes = Bytes.alloc(33);
63 |
64 | bytes.setInt64(0, _seed);
65 | bytes.setInt64(8, _state0);
66 | bytes.setInt64(16, _state1);
67 | bytes.set(24, _currentAvailable ? 1 : 0);
68 |
69 | if (_currentAvailable) {
70 | bytes.setInt64(25, _current);
71 | }
72 |
73 | return bytes;
74 | }
75 |
76 | function set_state(value:Bytes):Bytes {
77 | if (value.length != 33) {
78 | throw 'Wrong state size ${value.length}';
79 | }
80 |
81 | _seed = value.getInt64(0);
82 | _state0 = value.getInt64(8);
83 | _state1 = value.getInt64(16);
84 | _currentAvailable = value.get(24) == 1 ? true : false;
85 |
86 | if (_currentAvailable) {
87 | _current = value.getInt64(25);
88 | }
89 |
90 | return value;
91 | }
92 |
93 | function stepNext() {
94 | var x = _state0;
95 | var y = _state1;
96 |
97 | _state0 = y;
98 | x ^= x << PARAMETER_A;
99 | _state1 = x ^ y ^ (x >> PARAMETER_B) ^ (y >> PARAMETER_C);
100 |
101 | _current = _state1 + y;
102 | }
103 |
104 | public function nextInt():Int {
105 | if (_currentAvailable) {
106 | _currentAvailable = false;
107 | return _current.low;
108 |
109 | } else {
110 | stepNext();
111 | _currentAvailable = true;
112 |
113 | return _current.high;
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/seedyrng/Seedy.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.io.Bytes;
4 | import haxe.io.Error;
5 | import haxe.io.Eof;
6 | import haxe.Int64;
7 |
8 |
9 | private typedef Args = {
10 | algorithm:String,
11 | seed:Int64
12 | };
13 |
14 |
15 | /**
16 | Convenience functions
17 | **/
18 | class Seedy {
19 | /**
20 | `Random` singleton.
21 | **/
22 | public static var instance:Random = newInstance();
23 |
24 | static function newInstance():Random {
25 | return new Random(new Xorshift128Plus());
26 | }
27 |
28 | #if sys
29 | /**
30 | Entry point for generating binary output to standard out.
31 |
32 | This is useful piping output to statistical testing programs.
33 | **/
34 | public static function main() {
35 | var generator:GeneratorInterface;
36 | var args = parseArgs();
37 |
38 | switch args.algorithm {
39 | case "GaloisLFSR32":
40 | generator = new GaloisLFSR32();
41 | case "xorshift128+":
42 | generator = new Xorshift128Plus();
43 | case "xorshift64+":
44 | generator = new Xorshift64Plus();
45 | default:
46 | throw "Unknown generator name";
47 | }
48 |
49 | var random = new Random(generator);
50 | var stdout = Sys.stdout();
51 | var buffer = Bytes.alloc(4 * 256);
52 |
53 | while (true) {
54 | for (index in 0...256) {
55 | var num = random.nextFullInt();
56 | buffer.setInt32(index * 4, num);
57 | }
58 |
59 | try {
60 | stdout.write(buffer);
61 | } catch (exception:Eof) {
62 | break;
63 | } catch (exception:Error) {
64 | break;
65 | }
66 | }
67 | }
68 |
69 | static function parseArgs():Args {
70 | var algorithm:String;
71 | var seed:Int64;
72 | var args = Sys.args();
73 |
74 | if (args.length >= 1) {
75 | algorithm = args[0];
76 | } else {
77 | algorithm = "xorshift128+";
78 | }
79 |
80 | if (args.length >= 2) {
81 | if (args[1] == "random") {
82 | seed = Int64.make(
83 | Random.randomSystemInt(),
84 | Random.randomSystemInt());
85 | } else {
86 | seed = Int64.parseString(args[1]);
87 | }
88 | } else {
89 | seed = Int64.ofInt(1);
90 | }
91 |
92 | return {
93 | algorithm: algorithm,
94 | seed: seed
95 | };
96 | }
97 | #end
98 |
99 | /**
100 | Returns a floating point number in the range [0, 1), That is, a number
101 | greater or equal to 0 and less than 1.
102 |
103 | See also `Random.random`.
104 | **/
105 | public static function random():Float {
106 | return instance.random();
107 | }
108 |
109 | /**
110 | Returns an integer within the given range [`lower`, `upper`]. That is,
111 | a number within `lower` inclusive and `upper` inclusive.
112 |
113 | See also `Random.randomInt`.
114 | **/
115 | public static function randomInt(lower:Int, upper:Int):Int {
116 | return instance.randomInt(lower, upper);
117 | }
118 |
119 | /**
120 | Returns a uniformly distributed floating point number within the
121 | given range [`lower`, `upper`). That is, a number within `lower`
122 | inclusive and `upper` exclusive.
123 |
124 | See also `Random.uniform`.
125 | **/
126 | public static function uniform(lower:Float, upper:Float):Float {
127 | return instance.uniform(lower, upper);
128 | }
129 |
130 | /**
131 | Returns an element from the given array.
132 |
133 | See also `Random.choice`.
134 | **/
135 | public static function choice(array:Array):T {
136 | return instance.choice(array);
137 | }
138 |
139 | /**
140 | Shuffles the elements, in-place, in the given array.
141 |
142 | See also `Random.shuffle`.
143 | **/
144 | public static function shuffle(array:Array) {
145 | instance.shuffle(array);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/seedyrng/Random.hx:
--------------------------------------------------------------------------------
1 | package seedyrng;
2 |
3 | import haxe.io.BytesBuffer;
4 | import haxe.crypto.Sha1;
5 | import haxe.Int64;
6 | import haxe.io.Bytes;
7 |
8 |
9 | /**
10 | General featured interface to a pseudorandom number generator.
11 | **/
12 | class Random implements GeneratorInterface {
13 | public var seed(get, set):Int64;
14 | public var state(get, set):Bytes;
15 | public var usesAllBits(get, never):Bool;
16 |
17 | var generator:GeneratorInterface;
18 |
19 | /**
20 | @param seed Optional 64-bit seed to initialize the state of the
21 | generator. If not given, a seed from `randomSystemInt` will be used.
22 | @param generator Optional generator instance. If not given,
23 | a xorshift128+ generator is used.
24 | **/
25 | public function new(?seed:Int64, ?generator:GeneratorInterface) {
26 | if (seed == null) {
27 | seed = Int64.make(randomSystemInt(), randomSystemInt());
28 | }
29 |
30 | if (generator == null) {
31 | generator = new Xorshift128Plus();
32 | }
33 |
34 | this.generator = generator;
35 | this.seed = seed;
36 | }
37 |
38 | /**
39 | Returns a (non-secure) random integer from the system.
40 |
41 | This method can be used construct and obtain a seed. Unlike
42 | regular `Sys.random`, all bits are usable.
43 |
44 | For high-quality (cryptographic) random numbers, see the
45 | trandom library (https://lib.haxe.org/p/trandom).
46 | **/
47 | public static function randomSystemInt():Int {
48 | // There used to be target specific code here, but it's best to put it
49 | // into the 'trandom' library.
50 |
51 | // Ensures all bits are filled
52 | var value =
53 | (Std.random(0xff) << 24) |
54 | (Std.random(0xff) << 16) |
55 | (Std.random(0xff) << 8) |
56 | Std.random(0xff);
57 |
58 | return value;
59 | }
60 |
61 | function get_seed():Int64 {
62 | return generator.seed;
63 | }
64 |
65 | function set_seed(value:Int64):Int64 {
66 | return generator.seed = value;
67 | }
68 |
69 | function get_state():Bytes {
70 | return generator.state;
71 | }
72 |
73 | function set_state(value:Bytes):Bytes {
74 | return generator.state = value;
75 | }
76 |
77 | function get_usesAllBits():Bool {
78 | return generator.usesAllBits;
79 | }
80 |
81 | public function nextInt():Int {
82 | return generator.nextInt();
83 | }
84 |
85 | /**
86 | Returns an integer where all bits are uniformly distributed.
87 | **/
88 | public function nextFullInt():Int {
89 | if (generator.usesAllBits) {
90 | return generator.nextInt();
91 | } else {
92 | var num1 = generator.nextInt();
93 | var num2 = generator.nextInt();
94 | // Swap 16 bits to cover cases such as MSB zero or number never 0
95 | num2 = num2 >>> 16 | num2 << 16;
96 | return num1 ^ num2;
97 | }
98 | }
99 |
100 | /**
101 | Derives and sets a seed using the given string.
102 |
103 | This method encodes the string (supposedly UTF-8 all on targets) and
104 | derives a seed using `setBytesSeed`.
105 | **/
106 | public function setStringSeed(seed:String) {
107 | setBytesSeed(Bytes.ofString(seed));
108 | }
109 |
110 | /**
111 | Derives and sets a seed using the given bytes.
112 |
113 | This method works by using the SHA-1 hashing algorithm. A 64-bit
114 | integer is obtained from the first 64 bits of the digest with the order
115 | of bytes in little endian encoding.
116 | **/
117 | public function setBytesSeed(seed:Bytes) {
118 | var hash = Sha1.make(seed);
119 | this.seed = hash.getInt64(0);
120 | }
121 |
122 | /**
123 | Returns a floating point number in the range [0, 1), That is, a number
124 | greater or equal to 0 and less than 1.
125 |
126 | The distribution is approximately uniform.
127 | **/
128 | public function random():Float {
129 | // Implementation based on https://crypto.stackexchange.com/a/31659
130 | // Using 53 bits
131 | var upper = nextFullInt() & 0x1fffff;
132 | var lower:UInt = nextFullInt();
133 | var floatNum = upper * Math.pow(2, 32) + lower;
134 | var result = floatNum * Math.pow(2, -53);
135 |
136 | return result;
137 | }
138 |
139 | /**
140 | Returns an integer within the given range [`lower`, `upper`]. That is,
141 | a number within `lower` inclusive and `upper` inclusive.
142 |
143 | This method uses `random()` which the distribution approximately
144 | uniform. For large ranges greater than 2^53, the distribution will
145 | be noticeably biased.
146 | **/
147 | public function randomInt(lower:Int, upper:Int):Int {
148 | // randomInt and uniform implementation based on
149 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
150 | return Math.floor(random() * (upper - lower + 1)) + lower;
151 | }
152 |
153 | /**
154 | Returns a uniformly distributed floating point number within the
155 | given range [`lower`, `upper`). That is, a number within `lower`
156 | inclusive and `upper` exclusive.
157 |
158 | Note when `lower` or `upper` approach precision limits, the
159 | returned number may equal `upper` due to rounding.
160 | **/
161 | public function uniform(lower:Float, upper:Float):Float {
162 | return random() * (upper - lower) + lower;
163 | }
164 |
165 | /**
166 | Returns an element from the given array.
167 | **/
168 | public function choice(array:Array):T {
169 | return array[randomInt(0, array.length - 1)];
170 | }
171 |
172 | /**
173 | Shuffles the elements, in-place, in the given array.
174 |
175 | The shuffle algorithm is modern Fisher-Yates. The underlying number
176 | generator limits the number of permutations possible.
177 | This means that using this method alone to shuffle a deck of 52 cards
178 | will not result in all possible outcomes.
179 | **/
180 | public function shuffle(array:Array) {
181 | // Implementation https://en.wikipedia.org/w/index.php?title=Fisher%E2%80%93Yates_shuffle&oldid=864477677
182 | for (index in 0...array.length - 1) {
183 | var randIndex = randomInt(index, array.length - 1);
184 | var tempA = array[index];
185 | var tempB = array[randIndex];
186 |
187 | array[index] = tempB;
188 | array[randIndex] = tempA;
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------