From 4367cf37e7b6191fc2b251d6ec8dd06e081a6c49 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:08:01 +0300 Subject: [PATCH 01/11] upd --- .github/workflows/ci.yml | 0 .gitignore | 0 .pubignore | 0 CHANGELOG.md | 0 LICENSE | 0 README.md | 0 analysis_options.yaml | 0 example/main.dart | 0 lib/src/00_errors.dart | 0 lib/src/00_ints.dart | 0 lib/src/00_jsnumbers.dart | 0 lib/src/20_seeding.dart | 0 lib/src/21_base32.dart | 51 ++++++++++++++------ lib/src/21_base64.dart | 0 lib/src/50_splitmix64.dart | 0 lib/src/60_mulberry32.dart | 0 lib/src/60_xorshift128.dart | 0 lib/src/60_xorshift128plus.dart | 0 lib/src/60_xorshift32.dart | 0 lib/src/60_xorshift64.dart | 0 lib/src/60_xoshiro128pp.dart | 0 lib/src/60_xoshiro256.dart | 0 lib/src/80_drandom.dart | 27 +++++++++++ lib/src/90_aliases.dart | 5 -- lib/xrandom.dart | 4 +- pubspec.yaml | 2 +- test/aliases_test.dart | 5 -- test/data/generated2.dart | 0 test/drandom_test.dart | 20 ++++++++ test/exp_lemire_test.dart | 0 test/experimental/70_exp_lemire_o_neill.dart | 0 test/helper.dart | 3 +- test/helper_test.dart | 0 test/int_test.dart | 0 test/madsen.dart | 0 test/mulberry32_test.dart | 0 test/node_unsuported_tests.dart | 0 test/numbers_js_vm_test.dart | 0 test/refdata.dart | 0 test/splitmix64_test.dart | 0 test/unirandom32_test.dart | 0 test/unirandom64_VM_test.dart | 0 test/xorshift128_test.dart | 0 test/xorshift128plus_test.dart | 0 test/xorshift32_test.dart | 0 test/xorshift64_test.dart | 0 test/xoshiro128pp_test.dart | 0 test/xoshiro256pp_test.dart | 0 test/xoshiro256ss_test.dart | 0 todo.txt | 0 50 files changed, 90 insertions(+), 27 deletions(-) mode change 100644 => 100755 .github/workflows/ci.yml mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .pubignore mode change 100644 => 100755 CHANGELOG.md mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 analysis_options.yaml mode change 100644 => 100755 example/main.dart mode change 100644 => 100755 lib/src/00_errors.dart mode change 100644 => 100755 lib/src/00_ints.dart mode change 100644 => 100755 lib/src/00_jsnumbers.dart mode change 100644 => 100755 lib/src/20_seeding.dart mode change 100644 => 100755 lib/src/21_base32.dart mode change 100644 => 100755 lib/src/21_base64.dart mode change 100644 => 100755 lib/src/50_splitmix64.dart mode change 100644 => 100755 lib/src/60_mulberry32.dart mode change 100644 => 100755 lib/src/60_xorshift128.dart mode change 100644 => 100755 lib/src/60_xorshift128plus.dart mode change 100644 => 100755 lib/src/60_xorshift32.dart mode change 100644 => 100755 lib/src/60_xorshift64.dart mode change 100644 => 100755 lib/src/60_xoshiro128pp.dart mode change 100644 => 100755 lib/src/60_xoshiro256.dart create mode 100644 lib/src/80_drandom.dart mode change 100644 => 100755 lib/src/90_aliases.dart mode change 100644 => 100755 lib/xrandom.dart mode change 100644 => 100755 pubspec.yaml mode change 100644 => 100755 test/aliases_test.dart mode change 100644 => 100755 test/data/generated2.dart create mode 100644 test/drandom_test.dart mode change 100644 => 100755 test/exp_lemire_test.dart mode change 100644 => 100755 test/experimental/70_exp_lemire_o_neill.dart mode change 100644 => 100755 test/helper.dart mode change 100644 => 100755 test/helper_test.dart mode change 100644 => 100755 test/int_test.dart mode change 100644 => 100755 test/madsen.dart mode change 100644 => 100755 test/mulberry32_test.dart mode change 100644 => 100755 test/node_unsuported_tests.dart mode change 100644 => 100755 test/numbers_js_vm_test.dart mode change 100644 => 100755 test/refdata.dart mode change 100644 => 100755 test/splitmix64_test.dart mode change 100644 => 100755 test/unirandom32_test.dart mode change 100644 => 100755 test/unirandom64_VM_test.dart mode change 100644 => 100755 test/xorshift128_test.dart mode change 100644 => 100755 test/xorshift128plus_test.dart mode change 100644 => 100755 test/xorshift32_test.dart mode change 100644 => 100755 test/xorshift64_test.dart mode change 100644 => 100755 test/xoshiro128pp_test.dart mode change 100644 => 100755 test/xoshiro256pp_test.dart mode change 100644 => 100755 test/xoshiro256ss_test.dart mode change 100644 => 100755 todo.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.pubignore b/.pubignore old mode 100644 new mode 100755 diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/analysis_options.yaml b/analysis_options.yaml old mode 100644 new mode 100755 diff --git a/example/main.dart b/example/main.dart old mode 100644 new mode 100755 diff --git a/lib/src/00_errors.dart b/lib/src/00_errors.dart old mode 100644 new mode 100755 diff --git a/lib/src/00_ints.dart b/lib/src/00_ints.dart old mode 100644 new mode 100755 diff --git a/lib/src/00_jsnumbers.dart b/lib/src/00_jsnumbers.dart old mode 100644 new mode 100755 diff --git a/lib/src/20_seeding.dart b/lib/src/20_seeding.dart old mode 100644 new mode 100755 diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart old mode 100644 new mode 100755 index fd37f77..d8b1ef0 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -70,25 +70,48 @@ abstract class RandomBase32 implements Random { : combineUpper53bitsJS(nextRaw32(), nextRaw32()); } - /// Generates a non-negative random integer uniformly distributed in - /// the range from 0, inclusive, to [max], exclusive. - /// - /// To make the distribution uniform, we use the so-called - /// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7). - /// - /// This implementation is slightly faster than the standard one for - /// all [max] values, except for [max], which are powers of two. + static final int _POW2_32 = 1 << 32; + @override int nextInt(int max) { - if (max < 1 || max > 0xFFFFFFFF) { - throw RangeError.range(max, 1, 0xFFFFFFFF); + // https://github.com/dart-lang/sdk/blob/6faa5f3bd00ad8cbc640b3fc80cf7466c002a7df/sdk/lib/_internal/wasm/lib/math_patch.dart + if (max <= 0 || max > _POW2_32) { + throw RangeError.range( + max, 1, _POW2_32, 'max', 'Must be positive and <= 2^32'); } - int r = nextRaw32(); - int m = max - 1; - for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} - return r; + if ((max & -max) == max) { + // Fast case for powers of two. + return nextRaw32() & (max - 1); + } + + int rnd32; + int result; + do { + rnd32 = nextRaw32(); + result = rnd32 % max; + } while ((rnd32 - result + max) > _POW2_32); + return result; } + // /// Generates a non-negative random integer uniformly distributed in + // /// the range from 0, inclusive, to [max], exclusive. + // /// + // /// To make the distribution uniform, we use the so-called + // /// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7). + // /// + // /// This implementation is slightly faster than the standard one for + // /// all [max] values, except for [max], which are powers of two. + // @override + // int nextInt(int max) { + // if (max < 1 || max > 0xFFFFFFFF) { + // throw RangeError.range(max, 1, 0xFFFFFFFF); + // } + // int r = nextRaw32(); + // int m = max - 1; + // for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} + // return r; + // } + /// Generates a random floating point value uniformly distributed /// in the range from 0.0, inclusive, to 1.0, exclusive. /// diff --git a/lib/src/21_base64.dart b/lib/src/21_base64.dart old mode 100644 new mode 100755 diff --git a/lib/src/50_splitmix64.dart b/lib/src/50_splitmix64.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_mulberry32.dart b/lib/src/60_mulberry32.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xorshift128.dart b/lib/src/60_xorshift128.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xorshift128plus.dart b/lib/src/60_xorshift128plus.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xorshift32.dart b/lib/src/60_xorshift32.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xorshift64.dart b/lib/src/60_xorshift64.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xoshiro128pp.dart b/lib/src/60_xoshiro128pp.dart old mode 100644 new mode 100755 diff --git a/lib/src/60_xoshiro256.dart b/lib/src/60_xoshiro256.dart old mode 100644 new mode 100755 diff --git a/lib/src/80_drandom.dart b/lib/src/80_drandom.dart new file mode 100644 index 0000000..ee19dd2 --- /dev/null +++ b/lib/src/80_drandom.dart @@ -0,0 +1,27 @@ +import '60_xoshiro128pp.dart'; + +class Drandom extends Xoshiro128pp { + Drandom() + : super(Xoshiro128pp.defaultSeedA, Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC, + Xoshiro128pp.defaultSeedD); + + /// Generates a non-negative random integer uniformly distributed in + /// the range from 0, inclusive, to [max], exclusive. + /// + /// To make the distribution uniform, we use the so-called + /// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7). + /// + /// This implementation is slightly faster than the standard one for + /// all [max] values, except for [max], which are powers of two. + // @override + // int nextInt(int max) { + // if (max < 1 || max > 0x7FFFFFFF) { + // throw RangeError.range(max, 1, 0x7FFFFFFF); + // } + // int r = nextRaw32(); + // int m = max - 1; + // for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} + // return r; + // } + +} diff --git a/lib/src/90_aliases.dart b/lib/src/90_aliases.dart old mode 100644 new mode 100755 index 8ef8b3c..7ed9218 --- a/lib/src/90_aliases.dart +++ b/lib/src/90_aliases.dart @@ -34,8 +34,3 @@ class Qrandom extends Xoshiro128pp { Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC, Xoshiro128pp.defaultSeedD); } -class Drandom extends Xoshiro128pp { - Drandom() - : super(Xoshiro128pp.defaultSeedA, Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC, - Xoshiro128pp.defaultSeedD); -} diff --git a/lib/xrandom.dart b/lib/xrandom.dart old mode 100644 new mode 100755 index 5e42ed8..4b855f5 --- a/lib/xrandom.dart +++ b/lib/xrandom.dart @@ -11,4 +11,6 @@ export 'src/60_xorshift32.dart' show Xorshift32; export 'src/60_xorshift64.dart' show Xorshift64; export 'src/60_xoshiro128pp.dart' show Xoshiro128pp; export 'src/60_xoshiro256.dart' show Xoshiro256pp, Xoshiro256ss; -export 'src/90_aliases.dart' show Xrandom, Qrandom, Drandom; + +export 'src/80_drandom.dart' show Drandom; +export 'src/90_aliases.dart' show Xrandom, Qrandom; diff --git a/pubspec.yaml b/pubspec.yaml old mode 100644 new mode 100755 index c5ee9c2..d6cd9ad --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: xrandom description: "Random number generators library focused on the consistency, performance and reproducibility" -version: 0.7.0+1 +version: 0.7.1 homepage: https://github.com/rtmigo/xrandom environment: diff --git a/test/aliases_test.dart b/test/aliases_test.dart old mode 100644 new mode 100755 index 53311a6..c5c6323 --- a/test/aliases_test.dart +++ b/test/aliases_test.dart @@ -43,12 +43,7 @@ void main() { expect(random.nextInt(1000), 904); }); - test('Drandom readme', () { - final random = Drandom(); - expect( List.generate(5, (_) => random.nextInt(100)), - [42, 17, 96, 23, 46] ); - }); void checkRespectsSeed(RandomBase32 Function(int seed) create) { expect( List.generate(3, (_) => create(123).nextRaw32()), diff --git a/test/data/generated2.dart b/test/data/generated2.dart old mode 100644 new mode 100755 diff --git a/test/drandom_test.dart b/test/drandom_test.dart new file mode 100644 index 0000000..553257b --- /dev/null +++ b/test/drandom_test.dart @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: (c) 2021-2022 Art Galkin +// SPDX-License-Identifier: MIT + +import 'package:test/test.dart'; +import 'package:xrandom/xrandom.dart'; // no imports for src should be here + +void main() { + test('Drandom readme', () { + final random = Drandom(); + expect( List.generate(5, (_) => random.nextInt(100)), + [42, 17, 96, 23, 46] ); + }); + + test('Drandom large', () { + final random = Drandom(); + expect( List.generate(5, (_) => random.nextInt(0x7FEEDDAA)), + [1686059242, 361797217, 1133571596, 465717623, 1522544346] ); + }); + +} \ No newline at end of file diff --git a/test/exp_lemire_test.dart b/test/exp_lemire_test.dart old mode 100644 new mode 100755 diff --git a/test/experimental/70_exp_lemire_o_neill.dart b/test/experimental/70_exp_lemire_o_neill.dart old mode 100644 new mode 100755 diff --git a/test/helper.dart b/test/helper.dart old mode 100644 new mode 100755 index 52c43ad..94f99bb --- a/test/helper.dart +++ b/test/helper.dart @@ -200,7 +200,7 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio final r = createRandom(); expect(() => r.nextInt(-1), throwsRangeError); expect(() => r.nextInt(0), throwsRangeError); - expect(() => r.nextInt(0xFFFFFFFF+1), throwsRangeError); + expect(() => r.nextInt(0xFFFFFFFF+2), throwsRangeError); // if (INT64_SUPPORTED) { // r.nextInt(0xFFFFFFFF + 1); // no errors // } else { @@ -210,6 +210,7 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio // no errors r.nextInt(1); r.nextInt(0xFFFFFFFF); + r.nextInt(0xFFFFFFFF+1); }); test('nextInt(2) works almost like next bool', () { diff --git a/test/helper_test.dart b/test/helper_test.dart old mode 100644 new mode 100755 diff --git a/test/int_test.dart b/test/int_test.dart old mode 100644 new mode 100755 diff --git a/test/madsen.dart b/test/madsen.dart old mode 100644 new mode 100755 diff --git a/test/mulberry32_test.dart b/test/mulberry32_test.dart old mode 100644 new mode 100755 diff --git a/test/node_unsuported_tests.dart b/test/node_unsuported_tests.dart old mode 100644 new mode 100755 diff --git a/test/numbers_js_vm_test.dart b/test/numbers_js_vm_test.dart old mode 100644 new mode 100755 diff --git a/test/refdata.dart b/test/refdata.dart old mode 100644 new mode 100755 diff --git a/test/splitmix64_test.dart b/test/splitmix64_test.dart old mode 100644 new mode 100755 diff --git a/test/unirandom32_test.dart b/test/unirandom32_test.dart old mode 100644 new mode 100755 diff --git a/test/unirandom64_VM_test.dart b/test/unirandom64_VM_test.dart old mode 100644 new mode 100755 diff --git a/test/xorshift128_test.dart b/test/xorshift128_test.dart old mode 100644 new mode 100755 diff --git a/test/xorshift128plus_test.dart b/test/xorshift128plus_test.dart old mode 100644 new mode 100755 diff --git a/test/xorshift32_test.dart b/test/xorshift32_test.dart old mode 100644 new mode 100755 diff --git a/test/xorshift64_test.dart b/test/xorshift64_test.dart old mode 100644 new mode 100755 diff --git a/test/xoshiro128pp_test.dart b/test/xoshiro128pp_test.dart old mode 100644 new mode 100755 diff --git a/test/xoshiro256pp_test.dart b/test/xoshiro256pp_test.dart old mode 100644 new mode 100755 diff --git a/test/xoshiro256ss_test.dart b/test/xoshiro256ss_test.dart old mode 100644 new mode 100755 diff --git a/todo.txt b/todo.txt old mode 100644 new mode 100755 From 9c888bcf29b9eee9d8556939602f07e18b289fde Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:12:42 +0300 Subject: [PATCH 02/11] upd --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0266c3..f5d0d6e 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.7.1 + +- fixed: `nextInt` for `max>=0x80000000` returned non-uniformly distributed numbers + # 0.7.0+1 - Added Xoshiro256** From 01202951eb529faa871a24db3136d854b1c27bce Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:13:05 +0300 Subject: [PATCH 03/11] upd --- test/refdata.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/refdata.dart b/test/refdata.dart index c412437..8c6615f 100755 --- a/test/refdata.dart +++ b/test/refdata.dart @@ -1,2 +1,3 @@ void addReferenceData( {String? algorithm, String? seed, String? type, List? values}) {} + From 9ef185ca5c93788601329c366bc35f78e86926c0 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:25:06 +0300 Subject: [PATCH 04/11] upd --- lib/src/21_base32.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart index d8b1ef0..9d45801 100755 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -70,7 +70,7 @@ abstract class RandomBase32 implements Random { : combineUpper53bitsJS(nextRaw32(), nextRaw32()); } - static final int _POW2_32 = 1 << 32; + static final int _POW2_32 = 4294967296; // it's (1 << 32), but for JS we must set constant @override int nextInt(int max) { From 32aec8f284ac0789b0cd0b6894c2eba0b3c7af84 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:39:40 +0300 Subject: [PATCH 05/11] upd --- lib/src/21_base32.dart | 26 ++++------------- test/helper.dart | 64 ++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart index 9d45801..93c3f39 100755 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -70,11 +70,14 @@ abstract class RandomBase32 implements Random { : combineUpper53bitsJS(nextRaw32(), nextRaw32()); } - static final int _POW2_32 = 4294967296; // it's (1 << 32), but for JS we must set constant + static final int _POW2_32 = 4294967296; // it's (1 << 32), but for JS we have to a constant + /// Generates a non-negative random integer uniformly distributed in + /// the range from 0, inclusive, to [max], exclusive. @override int nextInt(int max) { - // https://github.com/dart-lang/sdk/blob/6faa5f3bd00ad8cbc640b3fc80cf7466c002a7df/sdk/lib/_internal/wasm/lib/math_patch.dart + // almost the same as https://bit.ly/35OH1Vh + if (max <= 0 || max > _POW2_32) { throw RangeError.range( max, 1, _POW2_32, 'max', 'Must be positive and <= 2^32'); @@ -93,25 +96,6 @@ abstract class RandomBase32 implements Random { return result; } - // /// Generates a non-negative random integer uniformly distributed in - // /// the range from 0, inclusive, to [max], exclusive. - // /// - // /// To make the distribution uniform, we use the so-called - // /// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7). - // /// - // /// This implementation is slightly faster than the standard one for - // /// all [max] values, except for [max], which are powers of two. - // @override - // int nextInt(int max) { - // if (max < 1 || max > 0xFFFFFFFF) { - // throw RangeError.range(max, 1, 0xFFFFFFFF); - // } - // int r = nextRaw32(); - // int m = max - 1; - // for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} - // return r; - // } - /// Generates a random floating point value uniformly distributed /// in the range from 0.0, inclusive, to 1.0, exclusive. /// diff --git a/test/helper.dart b/test/helper.dart index 94f99bb..4c6c788 100755 --- a/test/helper.dart +++ b/test/helper.dart @@ -169,7 +169,6 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio expect(zeroFound, isTrue); }); - test('Seed is different each time', () { // even with different seeds, we can get rare matches of results. // But most of the the results should be unique @@ -180,32 +179,11 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio greaterThan(90)); }); - // test('Huge ints: (1<<32)', - // () => checkHugeInts(createRandom(), 4294967296)); - // // "the fast case for powers of two" - // test('Huge ints: 0x80000000', - // () => checkHugeInts(createRandom(), 0x80000000)); - // - // test('Huge ints: JS_MAX_SAFE_INTEGER', - // () => checkHugeInts(createRandom(), JS_MAX_SAFE_INTEGER)); - // - // if (INT64_SUPPORTED) { - // test('Huge ints: 0x7FFFFFFFFFFFFFFF', - // () => checkHugeInts(createRandom(), int.parse('0x7FFFFFFFFFFFFFFF'))); - // } - - - test('nextIntCheckRange', () { final r = createRandom(); expect(() => r.nextInt(-1), throwsRangeError); expect(() => r.nextInt(0), throwsRangeError); expect(() => r.nextInt(0xFFFFFFFF+2), throwsRangeError); - // if (INT64_SUPPORTED) { - // r.nextInt(0xFFFFFFFF + 1); // no errors - // } else { - // expect(() => r.nextInt(0xFFFFFFFF + 1), throwsRangeError); - // } // no errors r.nextInt(1); @@ -273,17 +251,10 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio expect(random2.nextRaw32(), b64.lower32()); }); - // test('nextFloat roughly compares to nextDouble', () { - // final r1 = createRandom(); - // final r2 = createRandom(); - // - // const N = 1000; - // for (int i = 0; i < N; ++i) { - // final d = r1.nextDouble(); - // var x = r.nextInt(1); - // expect(x, 0); - // } - // }); + test('large ints uniformity', () { + final r = createRandom(); + checkUniformityForLargeInts(r); + }); }); } @@ -326,6 +297,33 @@ void checkBooleans(Random r) { expect(countTrue, lessThan(N * 0.6)); } +void checkUniformityForLargeInts(Random random) { + + // making sure that there is no problem described here: + // https://github.com/rtmigo/xrandom_dart/issues/3 + + const mid = 1431655765; // (1 << 32) ~/ 3; + const max = mid * 2; + var lower = 0; + var upper = 0; + for (var i = 0; i < 10000000; i++) { + if (random.nextInt(max) < mid) { + lower++; + } else { + upper++; + } + } + + const int expected = 5000000; + const int delta = 100000; + + expect(lower, greaterThan(expected-delta)); + expect(lower, lessThan(expected+delta)); + expect(upper, greaterThan(expected-delta)); + expect(upper, lessThan(expected+delta)); +} + + void checkIntegers(Random r) { int countMin = 0; int countMax = 0; From 6aed7e3134ab6485cc74a851950e7b2dec52f4b7 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:42:17 +0300 Subject: [PATCH 06/11] upd --- test/helper.dart | 74 +++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/test/helper.dart b/test/helper.dart index 4c6c788..8930084 100755 --- a/test/helper.dart +++ b/test/helper.dart @@ -16,7 +16,6 @@ const FAST = INT64_SUPPORTED; Map refData(String algo, String seedId) { - switch (algo) { case 'xoshiro256pp': algo = 'xoshiro256++'; @@ -30,13 +29,11 @@ Map refData(String algo, String seedId) { case 'xorshift128p': algo = 'xorshift128+'; break; - } for (final m in referenceData) { if (m['sample_class'] == algo && - m['sample_name'] == seedId) - { + m['sample_name'] == seedId) { return m; } } @@ -65,16 +62,14 @@ extension RdataExt on Map { } - void checkDoornikRandbl32(RandomBase32 Function() createRandom, String seedId) { - test('doornik_randbl_32 ${createRandom().runtimeType} seedId=$seedId', () - { + test('doornik_randbl_32 ${createRandom().runtimeType} seedId=$seedId', () { final random = createRandom(); final filePrefix = random.runtimeType.toString().toLowerCase(); final values = refData(filePrefix, seedId).doornik(); for (final refItem in enumerate(values)) { - assert(0<=refItem.value); - assert(refItem.value<1.0); + assert(0 <= refItem.value); + assert(refItem.value < 1.0); expect(random.nextFloat(), refItem.value, reason: 'refitem ${refItem.index}'); } @@ -106,7 +101,7 @@ void checkReferenceFiles(RandomBase32 Function() createRandom, String seedId) { test('toDouble', () { - final values = (random is RandomBase64) + final values = (random is RandomBase64) ? refData(filePrefix, seedId).double_multi() : refData(filePrefix, seedId).double_memcast(); int idx = 0; @@ -119,7 +114,7 @@ void checkReferenceFiles(RandomBase32 Function() createRandom, String seedId) { if (createRandom() is RandomBase64) { test('nextDoubleBitcast', () { final values = - refData(filePrefix, seedId).double_memcast(); + refData(filePrefix, seedId).double_memcast(); int idx = 0; for (final value in values) { expect((random as RandomBase64).nextDoubleBitcast(), value, @@ -127,12 +122,11 @@ void checkReferenceFiles(RandomBase32 Function() createRandom, String seedId) { } }); } - - }); } -List expectedList(RandomBase32 r) => [ +List expectedList(RandomBase32 r) => + [ (r is RandomBase64) ? r.nextRaw64() : r.nextRaw32(), r.nextInt(100000), r.nextDouble(), @@ -145,9 +139,9 @@ String trimLeadingZeros(String s) { return s.replaceAll(RegExp(r'^0+(?=.)'), ''); } -void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Function() createExpectedRandom) { +void testCommonRandom(RandomBase32 Function() createRandom, + RandomBase32 Function() createExpectedRandom) { group('Common random ${createRandom().runtimeType}', () { - test('nextDouble', () => checkDoubles(createRandom(), true)); test('nextFloat', () => checkDoubles(createRandom(), false)); @@ -157,15 +151,14 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio test('ints when power of two', () { final r = createExpectedRandom(); bool zeroFound = false; - for (int i=0; i<1000; ++i) - { - int x = r.nextInt(128); - expect(x, greaterThanOrEqualTo(0)); - expect(x, lessThan(128)); - if (x==0) { - zeroFound = true; - } + for (int i = 0; i < 1000; ++i) { + int x = r.nextInt(128); + expect(x, greaterThanOrEqualTo(0)); + expect(x, lessThan(128)); + if (x == 0) { + zeroFound = true; } + } expect(zeroFound, isTrue); }); @@ -173,7 +166,8 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio // even with different seeds, we can get rare matches of results. // But most of the the results should be unique expect( - List.generate(100, (index) => createRandom().nextDouble()) + List + .generate(100, (index) => createRandom().nextDouble()) .toSet() .length, greaterThan(90)); @@ -183,12 +177,12 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio final r = createRandom(); expect(() => r.nextInt(-1), throwsRangeError); expect(() => r.nextInt(0), throwsRangeError); - expect(() => r.nextInt(0xFFFFFFFF+2), throwsRangeError); + expect(() => r.nextInt(0xFFFFFFFF + 2), throwsRangeError); // no errors r.nextInt(1); r.nextInt(0xFFFFFFFF); - r.nextInt(0xFFFFFFFF+1); + r.nextInt(0xFFFFFFFF + 1); }); test('nextInt(2) works almost like next bool', () { @@ -228,14 +222,13 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio }); test('next32 <-> next64', () { - // we don't specify, whether the generator 64 bit or 32 bit, // so which method returns the generator output and which // returns the split or combined value if (!INT64_SUPPORTED) { - expect(()=>createExpectedRandom().nextRaw64(), throwsA(isA())); - return; + expect(() => createExpectedRandom().nextRaw64(), throwsA(isA())); + return; } // It must work both ways equally @@ -298,15 +291,15 @@ void checkBooleans(Random r) { } void checkUniformityForLargeInts(Random random) { - - // making sure that there is no problem described here: + // eliminating the issue: // https://github.com/rtmigo/xrandom_dart/issues/3 const mid = 1431655765; // (1 << 32) ~/ 3; const max = mid * 2; var lower = 0; var upper = 0; - for (var i = 0; i < 10000000; i++) { + const N = 10000000; + for (var i = 0; i < N; i++) { if (random.nextInt(max) < mid) { lower++; } else { @@ -315,12 +308,15 @@ void checkUniformityForLargeInts(Random random) { } const int expected = 5000000; - const int delta = 100000; - - expect(lower, greaterThan(expected-delta)); - expect(lower, lessThan(expected+delta)); - expect(upper, greaterThan(expected-delta)); - expect(upper, lessThan(expected+delta)); + const int delta = 100000; + + assert(expected * 2 == N); + assert(delta * 50 == expected); + + expect(lower, greaterThan(expected - delta)); + expect(lower, lessThan(expected + delta)); + expect(upper, greaterThan(expected - delta)); + expect(upper, lessThan(expected + delta)); } From cf5f125612115b9e699e682cd489c2fcf3d6cad8 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:50:09 +0300 Subject: [PATCH 07/11] publish --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d0d6e..6c05d6f 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 0.7.1 -- fixed: `nextInt` for `max>=0x80000000` returned non-uniformly distributed numbers +- fixed: `nextInt` results for `max >= 0x80000000` were not uniformly distributed # 0.7.0+1 From 14598fd03244b8b54b5ea814458fa7efe01130a8 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:51:00 +0300 Subject: [PATCH 08/11] publish --- lib/src/21_base32.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart index 93c3f39..97ed6b4 100755 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -155,3 +155,4 @@ abstract class RandomBase32 implements Random { @protected int boolCache_prevShift = 0; } + From 67300740f6e8e4dd353da2d7ea32009c0fa4f850 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 01:57:38 +0300 Subject: [PATCH 09/11] publish --- lib/src/21_base32.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart index 97ed6b4..9f4bfd4 100755 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -70,7 +70,7 @@ abstract class RandomBase32 implements Random { : combineUpper53bitsJS(nextRaw32(), nextRaw32()); } - static final int _POW2_32 = 4294967296; // it's (1 << 32), but for JS we have to a constant + static final int _POW2_32 = 4294967296; // it's (1 << 32). For JS it's safer to set a constant /// Generates a non-negative random integer uniformly distributed in /// the range from 0, inclusive, to [max], exclusive. From 704bc1e0d34f4ff89fba035af5445a8c49d7cd03 Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 02:03:25 +0300 Subject: [PATCH 10/11] publish --- CHANGELOG.md | 1 + lib/src/80_drandom.dart | 20 ++++++++++---------- test/drandom_test.dart | 7 +++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c05d6f..ff225cd 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.7.1 - fixed: `nextInt` results for `max >= 0x80000000` were not uniformly distributed +- narrowed the range of possible `max` values for `Drandom` # 0.7.0+1 diff --git a/lib/src/80_drandom.dart b/lib/src/80_drandom.dart index ee19dd2..417e553 100644 --- a/lib/src/80_drandom.dart +++ b/lib/src/80_drandom.dart @@ -13,15 +13,15 @@ class Drandom extends Xoshiro128pp { /// /// This implementation is slightly faster than the standard one for /// all [max] values, except for [max], which are powers of two. - // @override - // int nextInt(int max) { - // if (max < 1 || max > 0x7FFFFFFF) { - // throw RangeError.range(max, 1, 0x7FFFFFFF); - // } - // int r = nextRaw32(); - // int m = max - 1; - // for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} - // return r; - // } + @override + int nextInt(int max) { + if (max < 1 || max > 0x80000000) { + throw RangeError.range(max, 1, 0x80000000); + } + int r = nextRaw32(); + int m = max - 1; + for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {} + return r; + } } diff --git a/test/drandom_test.dart b/test/drandom_test.dart index 553257b..10ac12d 100644 --- a/test/drandom_test.dart +++ b/test/drandom_test.dart @@ -17,4 +17,11 @@ void main() { [1686059242, 361797217, 1133571596, 465717623, 1522544346] ); }); + test('Drandom nextInt range', () { + final random = Drandom(); + + expect(()=>random.nextInt(0x80000000), throwsRangeError); // no problem + random.nextInt(0x80000001); // problem + }); + } \ No newline at end of file From 8276e03abcde7a8d9b4fde25f7301621c35a6b9d Mon Sep 17 00:00:00 2001 From: Artem IG Date: Mon, 14 Mar 2022 02:07:26 +0300 Subject: [PATCH 11/11] publish --- test/drandom_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/drandom_test.dart b/test/drandom_test.dart index 10ac12d..387a15c 100644 --- a/test/drandom_test.dart +++ b/test/drandom_test.dart @@ -20,8 +20,8 @@ void main() { test('Drandom nextInt range', () { final random = Drandom(); - expect(()=>random.nextInt(0x80000000), throwsRangeError); // no problem - random.nextInt(0x80000001); // problem + expect(()=>random.nextInt(0x80000001), throwsRangeError); + random.nextInt(0x80000000); // does not throw anything }); } \ No newline at end of file