Skip to content

Commit

Permalink
Merge 8276e03 into master
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Mar 13, 2022
2 parents 8b4b47b + 8276e03 commit 99c8585
Show file tree
Hide file tree
Showing 50 changed files with 141 additions and 86 deletions.
Empty file modified .github/workflows/ci.yml
100644 → 100755
Empty file.
Empty file modified .gitignore
100644 → 100755
Empty file.
Empty file modified .pubignore
100644 → 100755
Empty file.
5 changes: 5 additions & 0 deletions CHANGELOG.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 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

- Added Xoshiro256**
Expand Down
Empty file modified LICENSE
100644 → 100755
Empty file.
Empty file modified README.md
100644 → 100755
Empty file.
Empty file modified analysis_options.yaml
100644 → 100755
Empty file.
Empty file modified example/main.dart
100644 → 100755
Empty file.
Empty file modified lib/src/00_errors.dart
100644 → 100755
Empty file.
Empty file modified lib/src/00_ints.dart
100644 → 100755
Empty file.
Empty file modified lib/src/00_jsnumbers.dart
100644 → 100755
Empty file.
Empty file modified lib/src/20_seeding.dart
100644 → 100755
Empty file.
32 changes: 20 additions & 12 deletions lib/src/21_base32.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,30 @@ abstract class RandomBase32 implements Random {
: combineUpper53bitsJS(nextRaw32(), nextRaw32());
}

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.
///
/// 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);
// 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');
}
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 random floating point value uniformly distributed
Expand Down Expand Up @@ -148,3 +155,4 @@ abstract class RandomBase32 implements Random {
@protected
int boolCache_prevShift = 0;
}

Empty file modified lib/src/21_base64.dart
100644 → 100755
Empty file.
Empty file modified lib/src/50_splitmix64.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_mulberry32.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xorshift128.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xorshift128plus.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xorshift32.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xorshift64.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xoshiro128pp.dart
100644 → 100755
Empty file.
Empty file modified lib/src/60_xoshiro256.dart
100644 → 100755
Empty file.
27 changes: 27 additions & 0 deletions lib/src/80_drandom.dart
Original file line number Diff line number Diff line change
@@ -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 > 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;
}

}
5 changes: 0 additions & 5 deletions lib/src/90_aliases.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 3 additions & 1 deletion lib/xrandom.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion pubspec.yaml
100644 → 100755
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
5 changes: 0 additions & 5 deletions test/aliases_test.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
Empty file modified test/data/generated2.dart
100644 → 100755
Empty file.
27 changes: 27 additions & 0 deletions test/drandom_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: (c) 2021-2022 Art Galkin <github.com/rtmigo>
// 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] );
});

test('Drandom nextInt range', () {
final random = Drandom();

expect(()=>random.nextInt(0x80000001), throwsRangeError);
random.nextInt(0x80000000); // does not throw anything
});

}
Empty file modified test/exp_lemire_test.dart
100644 → 100755
Empty file.
Empty file modified test/experimental/70_exp_lemire_o_neill.dart
100644 → 100755
Empty file.
119 changes: 57 additions & 62 deletions test/helper.dart
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const FAST = INT64_SUPPORTED;


Map refData(String algo, String seedId) {

switch (algo) {
case 'xoshiro256pp':
algo = 'xoshiro256++';
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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}');
}
Expand Down Expand Up @@ -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;
Expand All @@ -119,20 +114,19 @@ 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,
reason: 'item ${idx++}');
}
});
}


});
}

List expectedList(RandomBase32 r) => [
List expectedList(RandomBase32 r) =>
[
(r is RandomBase64) ? r.nextRaw64() : r.nextRaw32(),
r.nextInt(100000),
r.nextDouble(),
Expand All @@ -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));

Expand All @@ -157,59 +151,38 @@ 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);
});


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
expect(
List.generate(100, (index) => createRandom().nextDouble())
List
.generate(100, (index) => createRandom().nextDouble())
.toSet()
.length,
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+1), throwsRangeError);
// if (INT64_SUPPORTED) {
// r.nextInt(0xFFFFFFFF + 1); // no errors
// } else {
// expect(() => r.nextInt(0xFFFFFFFF + 1), throwsRangeError);
// }
expect(() => r.nextInt(0xFFFFFFFF + 2), throwsRangeError);

// no errors
r.nextInt(1);
r.nextInt(0xFFFFFFFF);
r.nextInt(0xFFFFFFFF + 1);
});

test('nextInt(2) works almost like next bool', () {
Expand Down Expand Up @@ -249,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<Unsupported64Error>()));
return;
expect(() => createExpectedRandom().nextRaw64(), throwsA(isA<Unsupported64Error>()));
return;
}

// It must work both ways equally
Expand All @@ -272,17 +244,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);
});
});
}

Expand Down Expand Up @@ -325,6 +290,36 @@ void checkBooleans(Random r) {
expect(countTrue, lessThan(N * 0.6));
}

void checkUniformityForLargeInts(Random random) {
// 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;
const N = 10000000;
for (var i = 0; i < N; i++) {
if (random.nextInt(max) < mid) {
lower++;
} else {
upper++;
}
}

const int expected = 5000000;
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));
}


void checkIntegers(Random r) {
int countMin = 0;
int countMax = 0;
Expand Down
Empty file modified test/helper_test.dart
100644 → 100755
Empty file.
Empty file modified test/int_test.dart
100644 → 100755
Empty file.
Empty file modified test/madsen.dart
100644 → 100755
Empty file.
Empty file modified test/mulberry32_test.dart
100644 → 100755
Empty file.
Empty file modified test/node_unsuported_tests.dart
100644 → 100755
Empty file.
Empty file modified test/numbers_js_vm_test.dart
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions test/refdata.dart
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
void addReferenceData(
{String? algorithm, String? seed, String? type, List? values}) {}

Empty file modified test/splitmix64_test.dart
100644 → 100755
Empty file.
Empty file modified test/unirandom32_test.dart
100644 → 100755
Empty file.
Empty file modified test/unirandom64_VM_test.dart
100644 → 100755
Empty file.
Empty file modified test/xorshift128_test.dart
100644 → 100755
Empty file.
Empty file modified test/xorshift128plus_test.dart
100644 → 100755
Empty file.
Empty file modified test/xorshift32_test.dart
100644 → 100755
Empty file.
Empty file modified test/xorshift64_test.dart
100644 → 100755
Empty file.
Empty file modified test/xoshiro128pp_test.dart
100644 → 100755
Empty file.
Empty file modified test/xoshiro256pp_test.dart
100644 → 100755
Empty file.
Empty file modified test/xoshiro256ss_test.dart
100644 → 100755
Empty file.
Empty file modified todo.txt
100644 → 100755
Empty file.

0 comments on commit 99c8585

Please sign in to comment.