Skip to content

Commit 99c8585

Browse files
Merge 8276e03 into master
2 parents 8b4b47b + 8276e03 commit 99c8585

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+141
-86
lines changed

.github/workflows/ci.yml

100644100755
File mode changed.

.gitignore

100644100755
File mode changed.

.pubignore

100644100755
File mode changed.

CHANGELOG.md

100644100755
+5

LICENSE

100644100755
File mode changed.

README.md

100644100755
File mode changed.

analysis_options.yaml

100644100755
File mode changed.

example/main.dart

100644100755
File mode changed.

lib/src/00_errors.dart

100644100755
File mode changed.

lib/src/00_ints.dart

100644100755
File mode changed.

lib/src/00_jsnumbers.dart

100644100755
File mode changed.

lib/src/20_seeding.dart

100644100755
File mode changed.

lib/src/21_base32.dart

100644100755
+20-12
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,30 @@ abstract class RandomBase32 implements Random {
7070
: combineUpper53bitsJS(nextRaw32(), nextRaw32());
7171
}
7272

73+
static final int _POW2_32 = 4294967296; // it's (1 << 32). For JS it's safer to set a constant
74+
7375
/// Generates a non-negative random integer uniformly distributed in
7476
/// the range from 0, inclusive, to [max], exclusive.
75-
///
76-
/// To make the distribution uniform, we use the so-called
77-
/// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7).
78-
///
79-
/// This implementation is slightly faster than the standard one for
80-
/// all [max] values, except for [max], which are powers of two.
8177
@override
8278
int nextInt(int max) {
83-
if (max < 1 || max > 0xFFFFFFFF) {
84-
throw RangeError.range(max, 1, 0xFFFFFFFF);
79+
// almost the same as https://bit.ly/35OH1Vh
80+
81+
if (max <= 0 || max > _POW2_32) {
82+
throw RangeError.range(
83+
max, 1, _POW2_32, 'max', 'Must be positive and <= 2^32');
8584
}
86-
int r = nextRaw32();
87-
int m = max - 1;
88-
for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {}
89-
return r;
85+
if ((max & -max) == max) {
86+
// Fast case for powers of two.
87+
return nextRaw32() & (max - 1);
88+
}
89+
90+
int rnd32;
91+
int result;
92+
do {
93+
rnd32 = nextRaw32();
94+
result = rnd32 % max;
95+
} while ((rnd32 - result + max) > _POW2_32);
96+
return result;
9097
}
9198

9299
/// Generates a random floating point value uniformly distributed
@@ -148,3 +155,4 @@ abstract class RandomBase32 implements Random {
148155
@protected
149156
int boolCache_prevShift = 0;
150157
}
158+

lib/src/21_base64.dart

100644100755
File mode changed.

lib/src/50_splitmix64.dart

100644100755
File mode changed.

lib/src/60_mulberry32.dart

100644100755
File mode changed.

lib/src/60_xorshift128.dart

100644100755
File mode changed.

lib/src/60_xorshift128plus.dart

100644100755
File mode changed.

lib/src/60_xorshift32.dart

100644100755
File mode changed.

lib/src/60_xorshift64.dart

100644100755
File mode changed.

lib/src/60_xoshiro128pp.dart

100644100755
File mode changed.

lib/src/60_xoshiro256.dart

100644100755
File mode changed.

lib/src/80_drandom.dart

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import '60_xoshiro128pp.dart';
2+
3+
class Drandom extends Xoshiro128pp {
4+
Drandom()
5+
: super(Xoshiro128pp.defaultSeedA, Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC,
6+
Xoshiro128pp.defaultSeedD);
7+
8+
/// Generates a non-negative random integer uniformly distributed in
9+
/// the range from 0, inclusive, to [max], exclusive.
10+
///
11+
/// To make the distribution uniform, we use the so-called
12+
/// [Debiased Modulo Once - Java Method](https://git.io/Jm0D7).
13+
///
14+
/// This implementation is slightly faster than the standard one for
15+
/// all [max] values, except for [max], which are powers of two.
16+
@override
17+
int nextInt(int max) {
18+
if (max < 1 || max > 0x80000000) {
19+
throw RangeError.range(max, 1, 0x80000000);
20+
}
21+
int r = nextRaw32();
22+
int m = max - 1;
23+
for (int u = r; u - (r = u % max) + m < 0; u = nextRaw32()) {}
24+
return r;
25+
}
26+
27+
}

lib/src/90_aliases.dart

100644100755
-5
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,3 @@ class Qrandom extends Xoshiro128pp {
3434
Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC, Xoshiro128pp.defaultSeedD);
3535
}
3636

37-
class Drandom extends Xoshiro128pp {
38-
Drandom()
39-
: super(Xoshiro128pp.defaultSeedA, Xoshiro128pp.defaultSeedB, Xoshiro128pp.defaultSeedC,
40-
Xoshiro128pp.defaultSeedD);
41-
}

lib/xrandom.dart

100644100755
+3-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ export 'src/60_xorshift32.dart' show Xorshift32;
1111
export 'src/60_xorshift64.dart' show Xorshift64;
1212
export 'src/60_xoshiro128pp.dart' show Xoshiro128pp;
1313
export 'src/60_xoshiro256.dart' show Xoshiro256pp, Xoshiro256ss;
14-
export 'src/90_aliases.dart' show Xrandom, Qrandom, Drandom;
14+
15+
export 'src/80_drandom.dart' show Drandom;
16+
export 'src/90_aliases.dart' show Xrandom, Qrandom;

pubspec.yaml

100644100755
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: xrandom
22
description: "Random number generators library focused on the consistency,
33
performance and reproducibility"
4-
version: 0.7.0+1
4+
version: 0.7.1
55
homepage: https://github.com/rtmigo/xrandom
66

77
environment:

test/aliases_test.dart

100644100755
-5
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,7 @@ void main() {
4343
expect(random.nextInt(1000), 904);
4444
});
4545

46-
test('Drandom readme', () {
47-
final random = Drandom();
48-
expect( List.generate(5, (_) => random.nextInt(100)),
49-
[42, 17, 96, 23, 46] );
5046

51-
});
5247

5348
void checkRespectsSeed(RandomBase32 Function(int seed) create) {
5449
expect( List.generate(3, (_) => create(123).nextRaw32()),

test/data/generated2.dart

100644100755
File mode changed.

test/drandom_test.dart

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: (c) 2021-2022 Art Galkin <github.com/rtmigo>
2+
// SPDX-License-Identifier: MIT
3+
4+
import 'package:test/test.dart';
5+
import 'package:xrandom/xrandom.dart'; // no imports for src should be here
6+
7+
void main() {
8+
test('Drandom readme', () {
9+
final random = Drandom();
10+
expect( List.generate(5, (_) => random.nextInt(100)),
11+
[42, 17, 96, 23, 46] );
12+
});
13+
14+
test('Drandom large', () {
15+
final random = Drandom();
16+
expect( List.generate(5, (_) => random.nextInt(0x7FEEDDAA)),
17+
[1686059242, 361797217, 1133571596, 465717623, 1522544346] );
18+
});
19+
20+
test('Drandom nextInt range', () {
21+
final random = Drandom();
22+
23+
expect(()=>random.nextInt(0x80000001), throwsRangeError);
24+
random.nextInt(0x80000000); // does not throw anything
25+
});
26+
27+
}

test/exp_lemire_test.dart

100644100755
File mode changed.

test/experimental/70_exp_lemire_o_neill.dart

100644100755
File mode changed.

test/helper.dart

100644100755
+57-62
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const FAST = INT64_SUPPORTED;
1616

1717

1818
Map refData(String algo, String seedId) {
19-
2019
switch (algo) {
2120
case 'xoshiro256pp':
2221
algo = 'xoshiro256++';
@@ -30,13 +29,11 @@ Map refData(String algo, String seedId) {
3029
case 'xorshift128p':
3130
algo = 'xorshift128+';
3231
break;
33-
3432
}
3533

3634
for (final m in referenceData) {
3735
if (m['sample_class'] == algo &&
38-
m['sample_name'] == seedId)
39-
{
36+
m['sample_name'] == seedId) {
4037
return m;
4138
}
4239
}
@@ -65,16 +62,14 @@ extension RdataExt on Map {
6562
}
6663

6764

68-
6965
void checkDoornikRandbl32(RandomBase32 Function() createRandom, String seedId) {
70-
test('doornik_randbl_32 ${createRandom().runtimeType} seedId=$seedId', ()
71-
{
66+
test('doornik_randbl_32 ${createRandom().runtimeType} seedId=$seedId', () {
7267
final random = createRandom();
7368
final filePrefix = random.runtimeType.toString().toLowerCase();
7469
final values = refData(filePrefix, seedId).doornik();
7570
for (final refItem in enumerate(values)) {
76-
assert(0<=refItem.value);
77-
assert(refItem.value<1.0);
71+
assert(0 <= refItem.value);
72+
assert(refItem.value < 1.0);
7873
expect(random.nextFloat(), refItem.value,
7974
reason: 'refitem ${refItem.index}');
8075
}
@@ -106,7 +101,7 @@ void checkReferenceFiles(RandomBase32 Function() createRandom, String seedId) {
106101

107102

108103
test('toDouble', () {
109-
final values = (random is RandomBase64)
104+
final values = (random is RandomBase64)
110105
? refData(filePrefix, seedId).double_multi()
111106
: refData(filePrefix, seedId).double_memcast();
112107
int idx = 0;
@@ -119,20 +114,19 @@ void checkReferenceFiles(RandomBase32 Function() createRandom, String seedId) {
119114
if (createRandom() is RandomBase64) {
120115
test('nextDoubleBitcast', () {
121116
final values =
122-
refData(filePrefix, seedId).double_memcast();
117+
refData(filePrefix, seedId).double_memcast();
123118
int idx = 0;
124119
for (final value in values) {
125120
expect((random as RandomBase64).nextDoubleBitcast(), value,
126121
reason: 'item ${idx++}');
127122
}
128123
});
129124
}
130-
131-
132125
});
133126
}
134127

135-
List expectedList(RandomBase32 r) => [
128+
List expectedList(RandomBase32 r) =>
129+
[
136130
(r is RandomBase64) ? r.nextRaw64() : r.nextRaw32(),
137131
r.nextInt(100000),
138132
r.nextDouble(),
@@ -145,9 +139,9 @@ String trimLeadingZeros(String s) {
145139
return s.replaceAll(RegExp(r'^0+(?=.)'), '');
146140
}
147141

148-
void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Function() createExpectedRandom) {
142+
void testCommonRandom(RandomBase32 Function() createRandom,
143+
RandomBase32 Function() createExpectedRandom) {
149144
group('Common random ${createRandom().runtimeType}', () {
150-
151145
test('nextDouble', () => checkDoubles(createRandom(), true));
152146
test('nextFloat', () => checkDoubles(createRandom(), false));
153147

@@ -157,59 +151,38 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio
157151
test('ints when power of two', () {
158152
final r = createExpectedRandom();
159153
bool zeroFound = false;
160-
for (int i=0; i<1000; ++i)
161-
{
162-
int x = r.nextInt(128);
163-
expect(x, greaterThanOrEqualTo(0));
164-
expect(x, lessThan(128));
165-
if (x==0) {
166-
zeroFound = true;
167-
}
154+
for (int i = 0; i < 1000; ++i) {
155+
int x = r.nextInt(128);
156+
expect(x, greaterThanOrEqualTo(0));
157+
expect(x, lessThan(128));
158+
if (x == 0) {
159+
zeroFound = true;
168160
}
161+
}
169162
expect(zeroFound, isTrue);
170163
});
171164

172-
173165
test('Seed is different each time', () {
174166
// even with different seeds, we can get rare matches of results.
175167
// But most of the the results should be unique
176168
expect(
177-
List.generate(100, (index) => createRandom().nextDouble())
169+
List
170+
.generate(100, (index) => createRandom().nextDouble())
178171
.toSet()
179172
.length,
180173
greaterThan(90));
181174
});
182175

183-
// test('Huge ints: (1<<32)',
184-
// () => checkHugeInts(createRandom(), 4294967296));
185-
// // "the fast case for powers of two"
186-
// test('Huge ints: 0x80000000',
187-
// () => checkHugeInts(createRandom(), 0x80000000));
188-
//
189-
// test('Huge ints: JS_MAX_SAFE_INTEGER',
190-
// () => checkHugeInts(createRandom(), JS_MAX_SAFE_INTEGER));
191-
//
192-
// if (INT64_SUPPORTED) {
193-
// test('Huge ints: 0x7FFFFFFFFFFFFFFF',
194-
// () => checkHugeInts(createRandom(), int.parse('0x7FFFFFFFFFFFFFFF')));
195-
// }
196-
197-
198-
199176
test('nextIntCheckRange', () {
200177
final r = createRandom();
201178
expect(() => r.nextInt(-1), throwsRangeError);
202179
expect(() => r.nextInt(0), throwsRangeError);
203-
expect(() => r.nextInt(0xFFFFFFFF+1), throwsRangeError);
204-
// if (INT64_SUPPORTED) {
205-
// r.nextInt(0xFFFFFFFF + 1); // no errors
206-
// } else {
207-
// expect(() => r.nextInt(0xFFFFFFFF + 1), throwsRangeError);
208-
// }
180+
expect(() => r.nextInt(0xFFFFFFFF + 2), throwsRangeError);
209181

210182
// no errors
211183
r.nextInt(1);
212184
r.nextInt(0xFFFFFFFF);
185+
r.nextInt(0xFFFFFFFF + 1);
213186
});
214187

215188
test('nextInt(2) works almost like next bool', () {
@@ -249,14 +222,13 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio
249222
});
250223

251224
test('next32 <-> next64', () {
252-
253225
// we don't specify, whether the generator 64 bit or 32 bit,
254226
// so which method returns the generator output and which
255227
// returns the split or combined value
256228

257229
if (!INT64_SUPPORTED) {
258-
expect(()=>createExpectedRandom().nextRaw64(), throwsA(isA<Unsupported64Error>()));
259-
return;
230+
expect(() => createExpectedRandom().nextRaw64(), throwsA(isA<Unsupported64Error>()));
231+
return;
260232
}
261233

262234
// It must work both ways equally
@@ -272,17 +244,10 @@ void testCommonRandom(RandomBase32 Function() createRandom, RandomBase32 Functio
272244
expect(random2.nextRaw32(), b64.lower32());
273245
});
274246

275-
// test('nextFloat roughly compares to nextDouble', () {
276-
// final r1 = createRandom();
277-
// final r2 = createRandom();
278-
//
279-
// const N = 1000;
280-
// for (int i = 0; i < N; ++i) {
281-
// final d = r1.nextDouble();
282-
// var x = r.nextInt(1);
283-
// expect(x, 0);
284-
// }
285-
// });
247+
test('large ints uniformity', () {
248+
final r = createRandom();
249+
checkUniformityForLargeInts(r);
250+
});
286251
});
287252
}
288253

@@ -325,6 +290,36 @@ void checkBooleans(Random r) {
325290
expect(countTrue, lessThan(N * 0.6));
326291
}
327292

293+
void checkUniformityForLargeInts(Random random) {
294+
// eliminating the issue:
295+
// https://github.com/rtmigo/xrandom_dart/issues/3
296+
297+
const mid = 1431655765; // (1 << 32) ~/ 3;
298+
const max = mid * 2;
299+
var lower = 0;
300+
var upper = 0;
301+
const N = 10000000;
302+
for (var i = 0; i < N; i++) {
303+
if (random.nextInt(max) < mid) {
304+
lower++;
305+
} else {
306+
upper++;
307+
}
308+
}
309+
310+
const int expected = 5000000;
311+
const int delta = 100000;
312+
313+
assert(expected * 2 == N);
314+
assert(delta * 50 == expected);
315+
316+
expect(lower, greaterThan(expected - delta));
317+
expect(lower, lessThan(expected + delta));
318+
expect(upper, greaterThan(expected - delta));
319+
expect(upper, lessThan(expected + delta));
320+
}
321+
322+
328323
void checkIntegers(Random r) {
329324
int countMin = 0;
330325
int countMax = 0;

test/helper_test.dart

100644100755
File mode changed.

test/int_test.dart

100644100755
File mode changed.

test/madsen.dart

100644100755
File mode changed.

test/mulberry32_test.dart

100644100755
File mode changed.

test/node_unsuported_tests.dart

100644100755
File mode changed.

test/numbers_js_vm_test.dart

100644100755
File mode changed.

test/refdata.dart

100644100755
+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
void addReferenceData(
22
{String? algorithm, String? seed, String? type, List? values}) {}
3+

test/splitmix64_test.dart

100644100755
File mode changed.

test/unirandom32_test.dart

100644100755
File mode changed.

test/unirandom64_VM_test.dart

100644100755
File mode changed.

test/xorshift128_test.dart

100644100755
File mode changed.

test/xorshift128plus_test.dart

100644100755
File mode changed.

test/xorshift32_test.dart

100644100755
File mode changed.

test/xorshift64_test.dart

100644100755
File mode changed.

test/xoshiro128pp_test.dart

100644100755
File mode changed.

test/xoshiro256pp_test.dart

100644100755
File mode changed.

test/xoshiro256ss_test.dart

100644100755
File mode changed.

todo.txt

100644100755
File mode changed.

0 commit comments

Comments
 (0)