More random.js tests and fix an issue in random.float()
This commit is contained in:
parent
1f62911e9e
commit
5dbe5b7fa6
4 changed files with 140 additions and 42 deletions
|
@ -77,10 +77,10 @@ function MersenneTwister()
|
||||||
y = y ^ ((y << 15) & 0xefc60000);
|
y = y ^ ((y << 15) & 0xefc60000);
|
||||||
y = y ^ (y >>> 18);
|
y = y ^ (y >>> 18);
|
||||||
|
|
||||||
return y;
|
return y >>> 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.real2 = function () {
|
this.real2 = function () {
|
||||||
return this.int32() * (1.0 / 4294967296.0);
|
return this.int32() * (1.0 / 4294967296.0);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,23 @@ var random = {
|
||||||
this.twister.seed(seed);
|
this.twister.seed(seed);
|
||||||
},
|
},
|
||||||
number: function (limit) {
|
number: function (limit) {
|
||||||
// Returns an integer in [0, limit]. Uniform distribution.
|
// Returns an integer in [0, limit). Uniform distribution.
|
||||||
if (limit == 0) {
|
if (limit == 0) {
|
||||||
return limit;
|
return limit;
|
||||||
}
|
}
|
||||||
if (limit == null || limit === undefined) {
|
if (limit == null || limit === undefined) {
|
||||||
limit = 0xffffffff;
|
limit = 0xffffffff;
|
||||||
}
|
}
|
||||||
return (this.twister.int32() >>> 0) % limit;
|
let x = (0x100000000 / limit) >>> 0,
|
||||||
|
y = (x * limit) >>> 0, r;
|
||||||
|
do {
|
||||||
|
r = this.twister.int32();
|
||||||
|
} while(y && r >= y);
|
||||||
|
return (r / x) >>> 0;
|
||||||
},
|
},
|
||||||
float: function () {
|
float: function () {
|
||||||
// Returns a float in [0, 1]. Uniform distribution.
|
// Returns a float in [0, 1). Uniform distribution.
|
||||||
return this.twister.real2() >>> 0;
|
return this.twister.real2();
|
||||||
},
|
},
|
||||||
range: function (start, limit) {
|
range: function (start, limit) {
|
||||||
// Returns an integer in [start, limit]. Uniform distribution.
|
// Returns an integer in [start, limit]. Uniform distribution.
|
||||||
|
@ -28,16 +33,16 @@ var random = {
|
||||||
Utils.traceback();
|
Utils.traceback();
|
||||||
throw new TypeError("random.range() received a non number type: '" + start + "', '" + limit + "')");
|
throw new TypeError("random.range() received a non number type: '" + start + "', '" + limit + "')");
|
||||||
}
|
}
|
||||||
return random.number(limit - start + 1) + start;
|
return this.number(limit - start + 1) + start;
|
||||||
},
|
},
|
||||||
ludOneTo: function(limit) {
|
ludOneTo: function(limit) {
|
||||||
// Returns a float in [1, limit]. The logarithm has uniform distribution.
|
// Returns a float in [1, limit]. The logarithm has uniform distribution.
|
||||||
return Math.exp(random.float() * Math.log(limit));
|
return Math.exp(this.float() * Math.log(limit));
|
||||||
},
|
},
|
||||||
item: function (list) {
|
item: function (list) {
|
||||||
if (!(list instanceof Array || (typeof list != "string" && "length" in list))) {
|
if (!(list instanceof Array || (typeof list != "string" && "length" in list))) {
|
||||||
Utils.traceback();
|
Utils.traceback();
|
||||||
throw new TypeError("random.item() received a non array type: '" + list + "'");
|
throw new TypeError("this.item() received a non array type: '" + list + "'");
|
||||||
}
|
}
|
||||||
return list[this.number(list.length)];
|
return list[this.number(list.length)];
|
||||||
},
|
},
|
||||||
|
@ -106,13 +111,13 @@ var random = {
|
||||||
return a;
|
return a;
|
||||||
},
|
},
|
||||||
use: function (obj) {
|
use: function (obj) {
|
||||||
return random.bool() ? obj : "";
|
return this.bool() ? obj : "";
|
||||||
},
|
},
|
||||||
shuffle: function (arr) {
|
shuffle: function (arr) {
|
||||||
let len = arr.length;
|
let len = arr.length;
|
||||||
let i = len;
|
let i = len;
|
||||||
while (i--) {
|
while (i--) {
|
||||||
let p = random.number(i + 1);
|
let p = this.number(i + 1);
|
||||||
let t = arr[i];
|
let t = arr[i];
|
||||||
arr[i] = arr[p];
|
arr[i] = arr[p];
|
||||||
arr[p] = t;
|
arr[p] = t;
|
||||||
|
@ -120,7 +125,7 @@ var random = {
|
||||||
},
|
},
|
||||||
shuffled: function (arr) {
|
shuffled: function (arr) {
|
||||||
let newArray = arr.slice();
|
let newArray = arr.slice();
|
||||||
random.shuffle(newArray);
|
this.shuffle(newArray);
|
||||||
return newArray;
|
return newArray;
|
||||||
},
|
},
|
||||||
subset: function (list, limit) {
|
subset: function (list, limit) {
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
/* XXX: translate some of the dieharder tests here? */
|
QUnit.test("MersenneTwister test uniform distribution", function(assert) {
|
||||||
|
const N = Math.pow(2, 17), expected = N * 1.35;
|
||||||
QUnit.test("MersenneTwister test distribution", function(assert) {
|
|
||||||
let mt = new MersenneTwister();
|
let mt = new MersenneTwister();
|
||||||
mt.seed(new Date().getTime());
|
mt.seed(new Date().getTime());
|
||||||
for (let i = 0; i < 100; ++i) {
|
let data = new Uint32Array(N);
|
||||||
let a = [], again = false;
|
for (let i = 0; i < data.length; ++i) {
|
||||||
for (let j = 0; j < 10; ++j) {
|
data[i] = mt.int32();
|
||||||
a[j] = mt.int32();
|
|
||||||
}
|
}
|
||||||
a.sort();
|
for (let sh = 0; sh <= 24; ++sh) {
|
||||||
for (let j = 0; j < (a.length - 1); ++j) {
|
let bins = new Uint32Array(256);
|
||||||
if (a[j] === a[j+1]) {
|
for (let b of data) {
|
||||||
again = true;
|
++bins[(b >>> sh) & 0xFF];
|
||||||
}
|
}
|
||||||
|
let variance = bins.reduce(function(a, v){ return a + Math.pow(v - N / bins.length, 2); }, 0);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance);
|
||||||
}
|
}
|
||||||
if (!again) {
|
});
|
||||||
assert.ok(true, "no dupes in 10 entries");
|
|
||||||
return;
|
QUnit.test("MersenneTwister test float distribution", function(assert) {
|
||||||
}
|
const N = Math.pow(2, 17), expected = N * 1.3;
|
||||||
}
|
let mt = new MersenneTwister();
|
||||||
assert.ok(false, "could not get unique entries");
|
mt.seed(new Date().getTime());
|
||||||
|
let bins = new Uint32Array(512);
|
||||||
|
for (let i = 0; i < N; ++i) {
|
||||||
|
++bins[(mt.real2() * bins.length) >>> 0];
|
||||||
|
}
|
||||||
|
let variance = bins.reduce(function(a, v){ return a + Math.pow(v - N / bins.length, 2); }, 0);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,23 +11,110 @@ QUnit.test("random.init() with provided seed", function(assert) {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
QUnit.test("random.init() is required", function(assert) {
|
||||||
|
assert.throws(random.number, /undefined/, "twister is uninitialized");
|
||||||
|
random.init(1);
|
||||||
|
random.number();
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("random.number() corner cases", function(assert) {
|
||||||
|
random.init(new Date().getTime());
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < 100; ++i)
|
||||||
|
sum += random.number(0);
|
||||||
|
assert.equal(sum, 0);
|
||||||
|
for (let i = 0; i < 100; ++i)
|
||||||
|
sum += random.number(1);
|
||||||
|
assert.equal(sum, 0);
|
||||||
|
let bins = new Uint32Array(2);
|
||||||
|
for (let i = 0; i < 100; ++i)
|
||||||
|
++bins[random.number(2)];
|
||||||
|
assert.equal(bins[0] + bins[1], 100);
|
||||||
|
assert.ok(bins[0] > 20);
|
||||||
|
sum = 0;
|
||||||
|
for (let i = 0; i < 12; ++i)
|
||||||
|
sum |= random.number();
|
||||||
|
assert.equal(sum>>>0, 0xFFFFFFFF);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("random.float() uniform distribution", function(assert) {
|
||||||
|
const N = Math.pow(2, 17), expected = N * 2;
|
||||||
|
random.init(new Date().getTime());
|
||||||
|
let bins = new Uint32Array(512), tmp;
|
||||||
|
for (let i = 0; i < N; ++i) {
|
||||||
|
tmp = (random.float() * bins.length) >>> 0;
|
||||||
|
if (tmp >= bins.length) throw "random.float() >= 1.0";
|
||||||
|
++bins[tmp];
|
||||||
|
}
|
||||||
|
let variance = bins.reduce(function(a, v){ return a + Math.pow(v - N / bins.length, 2); }, 0);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("random.range() uniform distribution", function(assert) {
|
||||||
|
const N = 10000, expected = N * 2;
|
||||||
|
let bins = new Uint32Array(50), tmp;
|
||||||
|
random.init(new Date().getTime());
|
||||||
|
for (let i = 0; i < N; ++i) {
|
||||||
|
tmp = random.range(0, bins.length - 1);
|
||||||
|
if (tmp >= bins.length) throw "random.range() > upper bound";
|
||||||
|
++bins[tmp];
|
||||||
|
}
|
||||||
|
let variance = bins.reduce(function(a, v){ return a + Math.pow(v - N / bins.length, 2); }, 0);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance);
|
||||||
|
});
|
||||||
|
|
||||||
QUnit.test("random.range() PRNG reproducibility", function(assert) {
|
QUnit.test("random.range() PRNG reproducibility", function(assert) {
|
||||||
let seed, result1, result2;
|
let seed, result1, result2;
|
||||||
seed = new Date().getTime();
|
seed = new Date().getTime();
|
||||||
|
for (let t = 0; t < 50; ++t) {
|
||||||
random.init(seed);
|
random.init(seed);
|
||||||
result1 = random.range(1, 20);
|
result1 = random.range(1, 20);
|
||||||
|
for (let i = 0; i < 5; ++i) {
|
||||||
random.init(seed);
|
random.init(seed);
|
||||||
result2 = random.range(1, 20);
|
result2 = random.range(1, 20);
|
||||||
assert.equal(result1, result2, "both results are the same")
|
assert.equal(result1, result2, "both results are the same")
|
||||||
|
}
|
||||||
|
seed = random.number();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test("random.choose() with equal distribution", function(assert) {
|
QUnit.test("random.choose() with equal distribution", function(assert) {
|
||||||
let foo = 0, bar = 0;
|
const N = 10000, expected = N * 3;
|
||||||
|
let bins = new Uint32Array(3), tmp;
|
||||||
random.init(new Date().getTime());
|
random.init(new Date().getTime());
|
||||||
for (let i = 0; i < 100; ++i) {
|
for (let i = 0; i < N; ++i) {
|
||||||
let tmp = random.choose([[1, 'foo'], [1, 'bar']]);
|
tmp = random.choose([[1, 0], [1, 1], [1, 2]]);
|
||||||
if (tmp == "foo") { foo += 1; }
|
if (tmp >= bins.length) throw "random.choose() > upper bound";
|
||||||
if (tmp == "bar") { bar += 1; }
|
++bins[tmp];
|
||||||
}
|
}
|
||||||
assert.ok(bar > 0 && foo > 0, "both objects were chosen")
|
let variance = Math.pow(bins[0] - N / 3, 2) + Math.pow(bins[1] - N / 3, 2) + Math.pow(bins[2] - N / 3, 2);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance + " (" + bins + ")");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QUnit.test("random.choose() with unequal distribution", function(assert) {
|
||||||
|
const N = 10000, expected = N * 3;
|
||||||
|
let bins = new Uint32Array(3), tmp;
|
||||||
|
random.init(new Date().getTime());
|
||||||
|
for (let i = 0; i < N; ++i) {
|
||||||
|
tmp = random.choose([[1, 0], [2, 1], [1, 2]]);
|
||||||
|
if (tmp >= bins.length) throw "random.choose() > upper bound";
|
||||||
|
++bins[tmp];
|
||||||
|
}
|
||||||
|
let variance = Math.pow(bins[0] - N / 4, 2) + Math.pow(bins[1] - N / 2, 2) + Math.pow(bins[2] - N / 4, 2);
|
||||||
|
assert.ok(variance < expected, "Expecting variance to be under " + expected + ", got " + variance + " (" + bins + ")");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
ludOneTo(limit)
|
||||||
|
item(list)
|
||||||
|
key(obj)
|
||||||
|
bool()
|
||||||
|
pick(obj)
|
||||||
|
chance(limit)
|
||||||
|
weighted(wa)
|
||||||
|
use(obj)
|
||||||
|
shuffle(arr)
|
||||||
|
shuffled(arr)
|
||||||
|
subset(list, limit)
|
||||||
|
choose(list, flat=true)
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in a new issue