├── .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 | --------------------------------------------------------------------------------