diff --git a/CHANGELOG.md b/CHANGELOG.md index ff225cd..60e1c69 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ +# 0.7.2 + +- Fixed: `Xoshiro256pp`, `Xoshiro256pp` and `Muberry32` were not throwing `Unsupported64Error` when + created in JS + # 0.7.1 -- fixed: `nextInt` results for `max >= 0x80000000` were not uniformly distributed -- narrowed the range of possible `max` values for `Drandom` +- Fixed: `nextInt` results for `max >= 0x80000000` were not uniformly distributed +- Narrowed the range of possible `max` values for `Drandom` # 0.7.0+1 - Added Xoshiro256** - -- fixed: for 64-bit generators `.nextInt32` was throwing an assertion error +- Fixed: for 64-bit generators `.nextInt32` was throwing an assertion error instead of returning 0 as a random result diff --git a/lib/src/21_base32.dart b/lib/src/21_base32.dart index 9f4bfd4..8f790db 100755 --- a/lib/src/21_base32.dart +++ b/lib/src/21_base32.dart @@ -76,12 +76,12 @@ abstract class RandomBase32 implements Random { /// the range from 0, inclusive, to [max], exclusive. @override int nextInt(int max) { - // almost the same as https://bit.ly/35OH1Vh + // almost the same as https://bit.ly/35OH1Vh by Dart authors, BSD if (max <= 0 || max > _POW2_32) { - throw RangeError.range( - max, 1, _POW2_32, 'max', 'Must be positive and <= 2^32'); + throw RangeError.range(max, 1, _POW2_32, 'max', 'Must be positive and <= 2^32'); } + if ((max & -max) == max) { // Fast case for powers of two. return nextRaw32() & (max - 1); @@ -155,4 +155,3 @@ abstract class RandomBase32 implements Random { @protected int boolCache_prevShift = 0; } - diff --git a/lib/src/60_mulberry32.dart b/lib/src/60_mulberry32.dart index ecb7e63..85e3824 100755 --- a/lib/src/60_mulberry32.dart +++ b/lib/src/60_mulberry32.dart @@ -3,11 +3,18 @@ import 'package:xrandom/src/21_base32.dart'; +import '00_errors.dart'; +import '00_ints.dart'; + /// Random number generator based on **mulberry32** algorithm by T. Ettinger. /// /// [reference](https://git.io/JmoUq) class Mulberry32 extends RandomBase32 { Mulberry32([int? seed32]) { + // although it's "32", it still needs long values + if (!INT64_SUPPORTED) { + throw Unsupported64Error(); + } if (seed32 != null) { RangeError.checkValueInInterval(seed32, 0, 0xFFFFFFFF); _state = seed32; diff --git a/lib/src/60_xoshiro256.dart b/lib/src/60_xoshiro256.dart index 684be68..a711e85 100755 --- a/lib/src/60_xoshiro256.dart +++ b/lib/src/60_xoshiro256.dart @@ -3,11 +3,16 @@ import 'package:xrandom/src/50_splitmix64.dart'; +import '00_errors.dart'; +import '00_ints.dart'; import '21_base64.dart'; /// Base class for Xoshiro256++ and Xoshiro256** abstract class Xoshiro256 extends RandomBase64 { Xoshiro256([int? seed64a, int? seed64b, int? seed64c, int? seed64d]) { + if (!INT64_SUPPORTED) { + throw Unsupported64Error(); + } if (seed64a != null || seed64b != null || seed64c != null || seed64d != null) { _S0 = seed64a!; _S1 = seed64b!; diff --git a/pubspec.yaml b/pubspec.yaml index 6a9db43..067cfa3 100755 --- 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.1 +version: 0.7.2 homepage: https://github.com/rtmigo/xrandom environment: diff --git a/test/aliases_test.dart b/test/aliases_test.dart index 3bc26a1..b1c9ebe 100755 --- a/test/aliases_test.dart +++ b/test/aliases_test.dart @@ -5,26 +5,19 @@ import 'package:test/test.dart'; import 'package:xrandom/xrandom.dart'; // no imports for src should be here void main() { - - - test('Xrandom', () { expect(Xrandom(), isA()); expect(Xrandom(1), isA()); expect(Xrandom.expected(), isA()); expect(Xrandom.expected(), isA()); - expect( - List.generate(3, (_) => Xrandom.expected().nextRaw32()), + expect(List.generate(3, (_) => Xrandom.expected().nextRaw32()), List.generate(3, (_) => Xorshift32.seeded().nextRaw32())); - expect( - List.generate(3, (_) => Xrandom(777).nextRaw32()), + expect(List.generate(3, (_) => Xrandom(777).nextRaw32()), List.generate(3, (_) => Xorshift32(777).nextRaw32())); final r1 = Xrandom(); final r2 = Xrandom(); - expect( - List.generate(3, (_) => r1.nextRaw32()), - isNot(List.generate(3, (_) => r2.nextRaw32()))); + expect(List.generate(3, (_) => r1.nextRaw32()), isNot(List.generate(3, (_) => r2.nextRaw32()))); }); test('Xrandom readme expected', () { @@ -43,13 +36,11 @@ void main() { expect(random.nextInt(1000), 904); }); - - void checkRespectsSeed(RandomBase32 Function(int seed) create) { - expect( List.generate(3, (_) => create(123).nextRaw32()), - List.generate(3, (_) => create(123).nextRaw32()) ); - expect( List.generate(3, (_) => create(123).nextRaw32()), - isNot(List.generate(3, (_) => create(321).nextRaw32())) ); + expect(List.generate(3, (_) => create(123).nextRaw32()), + List.generate(3, (_) => create(123).nextRaw32())); + expect(List.generate(3, (_) => create(123).nextRaw32()), + isNot(List.generate(3, (_) => create(321).nextRaw32()))); } test('XrandomHq respects seed argument', () { @@ -60,17 +51,13 @@ void main() { checkRespectsSeed((seed) => Xrandom(seed)); }); - test('XrandomHq returns constant values from seed', () { final random = Qrandom(10); - expect( List.generate(3, (_) => random.nextRaw32()), - [1282276250, 3989185767, 2009065675] ); + expect(List.generate(3, (_) => random.nextRaw32()), [1282276250, 3989185767, 2009065675]); }); test('XrandomHq range checking', () { - expect(()=>Qrandom(0), throwsRangeError); - expect(()=>Qrandom(0xFFFFFFFF+1), throwsRangeError); + expect(() => Qrandom(0), throwsRangeError); + expect(() => Qrandom(0xFFFFFFFF + 1), throwsRangeError); }); - - } diff --git a/test/helper.dart b/test/common.dart similarity index 83% rename from test/helper.dart rename to test/common.dart index 7635ec7..c7d45c8 100755 --- a/test/helper.dart +++ b/test/common.dart @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: (c) 2021 Art Galkin +// SPDX-FileCopyrightText: (c) 2021-2022 Art Galkin // SPDX-License-Identifier: MIT import 'dart:math'; @@ -146,7 +146,7 @@ void testCommonRandom(RandomBase32 Function() createRandom, test('nextFloat', () => checkDoubles(createRandom(), false)); test('bools', () => checkBooleans(createRandom())); - test('ints', () => checkIntegers(createRandom())); + test('ints', () => check_nextInt_bounds(createRandom())); test('ints when power of two', () { final r = createExpectedRandom(); @@ -243,10 +243,21 @@ void testCommonRandom(RandomBase32 Function() createRandom, expect(random2.nextRaw32(), b64.lower32()); }); - test('large ints uniformity', () { + test('nextInt returns uniform result for max > 1<<32', () { final r = createRandom(); - checkUniformityForLargeInts(r); + check_nextInt_is_uniform_for_large_max(r); }); + + var r = Random(); + for (int i=0; i<10; ++i) { + // generating max from range 1000..(1<<32) + int max = 0; + while ((max = r.nextInt(0xFFFFFFFF+1)+1)<1000) {} + + test('nextInt returns uniform results for max=$max', () { + check_nextInt_is_uniform(createRandom(), max); + }); + } }); } @@ -289,7 +300,9 @@ void checkBooleans(Random r) { expect(countTrue, lessThan(N * 0.6)); } -void checkUniformityForLargeInts(Random random) { +void check_nextInt_is_uniform_for_large_max(Random random) { + // checking whether nextInt results are uniform for max exceeding 31<<1 + // // eliminating the issue: // https://github.com/rtmigo/xrandom_dart/issues/3 @@ -318,8 +331,51 @@ void checkUniformityForLargeInts(Random random) { expect(upper, lessThan(expected + delta)); } +void check_nextInt_is_uniform(Random random, int max) { + // we will split range (0..max) to three equal bins: (0..a) (a..b) (b..max) + // Then we generate random ints from (0..max), and counting how many results correspond + // to particular bin. If the distribution is uniform, we'll get roughly the same count + // of results in each bin. + + int a = (max * (1 / 3)).round(); + int b = (max * (2 / 3)).round(); + + assert (0 < a); + assert (a < b); + assert (b < max); + + int countA=0, countB=0, countC=0; + + const N = 10000000; + + for (int i=0; i 2147483645 // 0x7ffffffe 2147483646 -> 2147483646 // 0x7fffffff 2147483647 -> 2147483647 @@ -124,7 +112,7 @@ void main() { // 0x80000001 2147483649 -> -2147483647 // 0x80000002 2147483650 -> -2147483646 - for (final pair in[ + for (final pair in [ [0x7ffffffd, 2147483645], [0x7ffffffe, 2147483646], [0x7fffffff, 2147483647], diff --git a/test/mulberry32_test.dart b/test/mulberry32_test.dart index 47ac4dc..4416f3c 100755 --- a/test/mulberry32_test.dart +++ b/test/mulberry32_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_mulberry32.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Mulberry32(), () => Mulberry32.seeded()); @@ -33,3 +33,4 @@ void main() { expect(List.generate(10, (_) => random.nextRaw32()), exp); }); } + diff --git a/test/node_unsuported_tests.dart b/test/node_unsuported_test.dart similarity index 78% rename from test/node_unsuported_tests.dart rename to test/node_unsuported_test.dart index 78b6380..9fd0d4a 100755 --- a/test/node_unsuported_tests.dart +++ b/test/node_unsuported_test.dart @@ -8,10 +8,13 @@ import 'package:xrandom/xrandom.dart'; void main() { test('64', () { + Xorshift32(); // no errors expect(() => Xorshift32().nextRaw64(), throwsA(isA())); expect(() => Xorshift64.seeded(), throwsA(isA())); expect(() => Xorshift128p.seeded(), throwsA(isA())); expect(() => Xoshiro256pp.seeded(), throwsA(isA())); + expect(() => Xoshiro256ss.seeded(), throwsA(isA())); expect(() => Splitmix64.seeded(), throwsA(isA())); + expect(() => Mulberry32.seeded(), throwsA(isA())); }); } diff --git a/test/splitmix64_test.dart b/test/splitmix64_test.dart index 8b073f0..39895c6 100755 --- a/test/splitmix64_test.dart +++ b/test/splitmix64_test.dart @@ -6,19 +6,15 @@ import 'package:test/test.dart'; import 'package:xrandom/src/50_splitmix64.dart'; import 'package:xrandom/xrandom.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Splitmix64(), ()=>Splitmix64.seeded()); - //print(-6562126107>>1); - //return; - checkReferenceFiles(() => Splitmix64(1), 'a'); checkReferenceFiles(() => Splitmix64(0), 'b'); checkReferenceFiles(() => Splitmix64(777), 'c'); checkReferenceFiles(() => Splitmix64(int.parse('0xf7d3b43bed078fa3')), 'd'); - // checkReferenceFiles(() => Splitmix64(3141592653589793238), 'c'); test('expected values', () { expect(expectedList(Splitmix64.seeded()), [ diff --git a/test/xorshift128_test.dart b/test/xorshift128_test.dart index 3b94295..278e5a5 100755 --- a/test/xorshift128_test.dart +++ b/test/xorshift128_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xorshift128.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xorshift128(), ()=>Xorshift128.seeded()); diff --git a/test/xorshift128plus_test.dart b/test/xorshift128plus_test.dart index 5710d56..81adbad 100755 --- a/test/xorshift128plus_test.dart +++ b/test/xorshift128plus_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xorshift128plus.dart'; -import 'helper.dart'; +import 'common.dart'; import 'madsen.dart'; void main() { diff --git a/test/xorshift32_test.dart b/test/xorshift32_test.dart index db95791..f5ee08d 100755 --- a/test/xorshift32_test.dart +++ b/test/xorshift32_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xorshift32.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xorshift32(), ()=>Xorshift32.seeded()); diff --git a/test/xorshift64_test.dart b/test/xorshift64_test.dart index 5962127..1db3483 100755 --- a/test/xorshift64_test.dart +++ b/test/xorshift64_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xorshift64.dart'; import 'package:xrandom/xrandom.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xorshift64(), ()=>Xorshift64.seeded()); @@ -15,17 +15,6 @@ void main() { checkReferenceFiles(() => Xorshift64(42), 'b'); checkReferenceFiles(() => Xorshift64(3141592653589793238), 'c'); - // test('expected values', () { - // expect(expectedList(Xorshift64.expected()), [ - // int.parse('-6926213550972868430'), - // 40031, - // 0.38167886102443327, - // false, - // true, - // false - // ]); - // }); - test('create without args', () async { final random1 = Xorshift64(); await Future.delayed(Duration(milliseconds: 2)); diff --git a/test/xoshiro128pp_test.dart b/test/xoshiro128pp_test.dart index 32cb093..1b06796 100755 --- a/test/xoshiro128pp_test.dart +++ b/test/xoshiro128pp_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xoshiro128pp.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xoshiro128pp(), ()=>Xoshiro128pp.seeded()); @@ -13,7 +13,6 @@ void main() { checkReferenceFiles( () => Xoshiro128pp(1081037251, 1975530394, 2959134556, 1579461830), 'c'); - test('expected values', () { expect(expectedList(Xoshiro128pp.seeded()), [1686059242, 97217, 0.26393020434967074, false, true, false]); diff --git a/test/xoshiro256pp_test.dart b/test/xoshiro256pp_test.dart index 240419c..48583e1 100755 --- a/test/xoshiro256pp_test.dart +++ b/test/xoshiro256pp_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xoshiro256.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xoshiro256pp(), ()=>Xoshiro256pp.seeded()); diff --git a/test/xoshiro256ss_test.dart b/test/xoshiro256ss_test.dart index 2076f7a..9751a63 100755 --- a/test/xoshiro256ss_test.dart +++ b/test/xoshiro256ss_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import 'package:xrandom/src/60_xoshiro256.dart'; -import 'helper.dart'; +import 'common.dart'; void main() { testCommonRandom(() => Xoshiro256ss(), ()=>Xoshiro256ss.seeded());