-
-
Save Pencroff/33575c9492e0c212b1c29cfc3be79eae to your computer and use it in GitHub Desktop.
Random.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var HAS_UINT32_ARRAY = !(typeof Uint32Array === 'undefined') | |
var USE_UINT32_ARRAY = false // Benchmarks show this to be slower than Array's. Hrmph. | |
var bits_per_word = 32 | |
, address_shift = 5 // log2(bits_per_word) | |
/** | |
* Create a new bit set. If a size is provided an array will be allocated | |
* upfront, which speeds up writes and test operations since bitwise operations | |
* against undefined require a cast. Providing a size will fix the bitset to | |
* exactly that size, whereas unsized bitsets grow automatically. | |
* | |
* The size of the BitSet is rounded up to the next multiple of 32 | |
* automatically. | |
* | |
* If the Typed Uint32Array is available it is used for fixed size sets. | |
**/ | |
var BitSet = function BitSet(size, words) { | |
if (!(this instanceof BitSet)) { | |
return new BitSet() | |
} | |
var fixed = (size ? true : false) | |
size = (fixed ? (Math.ceil(size / bits_per_word) * bits_per_word) : void 0) | |
if (!(typeof words === 'undefined')) { | |
this.words = words | |
} else if (fixed) { | |
var word_length = size / bits_per_word; | |
if (USE_UINT32_ARRAY) { | |
var buffer = new Buffer(word_length * 4) | |
buffer.fill(0) | |
this.words = new Uint32Array(buffer) | |
} else { | |
this.words = new Array(word_length) | |
for (var i = 0; i < word_length; i++) { | |
this.words[i] = 0 | |
} | |
} | |
} else { | |
this.words = [] | |
} | |
var index = function(position) { | |
if (fixed && position >= size) { | |
throw new Error('position ' + position + ' exceeds size ' + size) | |
} | |
return position >> address_shift | |
} | |
this.word_length = function() { | |
if (fixed) { | |
return this.words.length | |
} else { | |
var length = this.words.length | |
for (var i = this.words.length - 1; i >= 0; i--) { | |
if (this.words[i] !== 0) { | |
break | |
} | |
length -= 1 | |
} | |
return length | |
} | |
} | |
this.set = function BitSet_set(position) { | |
return this.words[index(position)] |= 1 << position | |
} | |
this.clear = function BitSet_clear(position) { | |
return this.words[index(position)] &= ~(1 << position) | |
} | |
this.get = function BitSet_get(position) { | |
return (this.words[index(position)] & (1 << position)) !== 0 | |
} | |
this.flip = function BitSet_flip(position) { | |
return this.words[index(position)] ^= (1 << position); | |
} | |
this.fixed = function BitSet_fixed() { | |
return fixed | |
} | |
this.size = function BitSet_size() { | |
return size | |
} | |
this.is_empty = function BitSet__is_empty() { | |
for (var i = 0, ii = this.word_length(); i < ii; i++) { | |
if (this.words[i]) { | |
return 0 | |
} | |
} | |
return 1 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<style> | |
html, body { | |
background: #FFF; | |
margin: 0; | |
} | |
#canvas { | |
position: fixed; | |
background: #FFF | |
} | |
#stats { | |
position: absolute; | |
bottom: 10px; | |
right: 10px; | |
z-index: 10; | |
width: 400px; | |
height: 2em; | |
vertical-align: middle; | |
line-height: 2em; | |
margin: 0px 10px; | |
font-family: helvetica, serif; | |
font-size: 14px; | |
text-align: center; | |
opacity: 0.4; | |
color: white; | |
background-color: black; | |
} | |
#stats:hover { | |
opacity: 1; | |
} | |
#controls { | |
position: absolute; | |
right: 10px; | |
top: 5px; | |
cursor: pointer; | |
} | |
</style> | |
</head> | |
<canvas id="canvas"></canvas> | |
<div id="stats"><span id="info"></span><button id="controls">❙❙</button></div> | |
<script src="./bitset.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/lib/xor4096.min.js"></script> | |
<script src="./Random.js"></script> | |
<script> | |
var rnd = new xor4096(Date.now().toString()); | |
var rndMt = new Random(Random.engines.mt19937().autoSeed()); | |
Number.prototype.prettyString = function() { | |
return (((this / 1e3) | 0) * 1e3).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |
} | |
window.onload = function() { | |
//Math.random = crypto_random; | |
// A place to show some statisitics | |
var stats = document.getElementById('info') | |
, ctrl = document.getElementById('controls'); | |
ctrl.style.display = 'none'; | |
/* | |
var generation_paused = false; | |
ctrl.onclick = function() { | |
generation_paused = !generation_paused; | |
if (generation_paused) { | |
ctrl.innerHTML = '►'; | |
} else { | |
ctrl.innerHTML = '❙❙'; | |
} | |
} | |
*/ | |
// The canvas element | |
var canvas = document.getElementById('canvas') | |
, ctx = canvas.getContext('2d'); | |
function resize() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
canvas.style.width = window.innerWidth + 'px'; | |
canvas.style.height = window.innerHeight + 'px'; | |
} | |
resize(); | |
window.onresize = resize; | |
// The number of segments we're cutting our [0,1)^2 hypercube into. | |
var segments = Math.pow(2, 15); | |
// Record of the number of collisions that have occurred at each hypercube % 2. | |
var collisions = new BitSet(segments * segments); | |
// How many random numbers, and how many points we've generated so far. | |
var numbers = 0 | |
, points = 0; | |
// The current time, when we're starting generation (used to generate some stats). | |
var gen_start = new Date().getTime(); | |
// Some parameters on how much time to spend generating random numbers and how | |
// often to check our time usage. We set these by sampling the speed of our | |
// animation over time, but these are static starting values. | |
var gen_time_slice = 5 | |
, gen_check_period = 1000; | |
// Some stats to help adjust parameters and whatnot. | |
var target_hz = 10 | |
, frame_interval_ms = Math.floor(1000/target_hz) | |
, date_checks = 0 | |
, frames = 0 | |
, time_per_frame = 0 | |
, blocks = 0 | |
, time_per_block = 0 | |
, total_pixels = 0 | |
, black_pixels = 0; | |
function noise(ctx) { | |
// Re-draw our viewport of the collisions, with a black pixel designating an odd | |
// number of collisions and a white pixel designating an even number (or 0). | |
var start = new Date().getTime(); | |
var w = ctx.canvas.width | |
, h = ctx.canvas.height; | |
var data = ctx.createImageData(w, h) | |
, buf = new Uint32Array(data.data.buffer); | |
black_pixels = 0; | |
total_pixels = 0; | |
for (var c = 0; c < w; c++) { | |
for (var r = 0; r < h; r++) { | |
var c_p = (r * segments) + c // The collision position in the collisions data structure | |
, b_p = (r * w) + c; // The pixel position in the image buffer | |
if (collisions.get(c_p)) { | |
buf[b_p] = 0xff000000; | |
black_pixels += 1; | |
} | |
total_pixels += 1; | |
} | |
} | |
ctx.putImageData(data, 0, 0); | |
frames += 1; | |
time_per_frame = (0.9 * time_per_frame) + (0.1 * (new Date().getTime() - start)); | |
} | |
(function generate_collisions() { | |
// Generate a bit more random noise. We'll do as much as we can in some fixed | |
// time period. Refresh rate is 60FPS so we only have ~16ms between frames. We'll | |
// try to keep this function from blocking for more than a subset of that time. We | |
// don't want to waste too much time futzing with Date objects either, so we only | |
// check how much time we've spent working periodically. | |
var start = new Date().getTime() | |
, checks = 0; | |
while ((new Date().getTime() - start) <= gen_time_slice) { | |
checks += 1; | |
for (var i = gen_check_period; i > 0; --i) { | |
var x = Math.floor(rndMt.real(0, 1, true) * segments) //rnd() Math.random() | |
, y = Math.floor(rndMt.real(0, 1, true) * segments) | |
, p = (x * segments) + y; | |
collisions.flip(p); | |
numbers += 2; | |
points += 1; | |
} | |
} | |
date_checks = (0.9 * date_checks) + (0.1 * checks); | |
blocks += 1; | |
time_per_block = (0.9 * time_per_block) + (0.1 * (new Date().getTime() - start)); | |
setTimeout(generate_collisions, 0); | |
})(); | |
var update_stats = function update_stats() { | |
var secs = (new Date().getTime() - gen_start) / 1000 | |
, rate = points / secs; | |
stats.innerHTML = points.prettyString() + ' points (' + rate.prettyString() + '/s). ' + (100*black_pixels/total_pixels).toPrecision(5) + '% black.'; | |
console.log('Stats at: ' + new Date().toString()); | |
console.log(' ' + date_checks + ' date checks.'); | |
console.log(' ' + frames + ' frames (' + (frames / secs) + '/s).'); | |
console.log(' ' + time_per_frame + 'ms / frame'); | |
console.log(' ' + blocks + ' blocks (' + (blocks / secs) + '/s).'); | |
console.log(' ' + time_per_block + 'ms / block'); | |
// Update how many numbers per refresh we generate and how often we check up on | |
// the amount of time we've spent generating. The goal is to pause our random | |
// number generation at about 4x the framerate (to make sure we don't block too | |
// long) and to check how much time we've spent generating about twice per block | |
// of RNGs generated (so we've got good bounds on expected time spent generating, | |
// but we're not wasting too much time looking at the clock). | |
gen_time_slice = ((frame_interval_ms - time_per_frame) / 4) | 0; | |
gen_check_period = Math.max(((gen_check_period * (date_checks / 2)) | 0), 2); | |
console.log(' New time slice: ' + gen_time_slice); | |
console.log(' New check period: ' + gen_check_period); | |
} | |
setInterval(update_stats, 1000); | |
(function loop() { | |
var start = new Date().getTime(); | |
noise(ctx); | |
var next = Math.max(0, frame_interval_ms - (new Date().getTime() - start)) | |
setTimeout(loop, next); | |
})(); | |
} | |
</script> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*jshint eqnull:true*/ | |
(function (root) { | |
"use strict"; | |
var GLOBAL_KEY = "Random"; | |
var imul = (typeof Math.imul !== "function" || Math.imul(0xffffffff, 5) !== -5 ? | |
function (a, b) { | |
var ah = (a >>> 16) & 0xffff; | |
var al = a & 0xffff; | |
var bh = (b >>> 16) & 0xffff; | |
var bl = b & 0xffff; | |
// the shift by 0 fixes the sign on the high part | |
// the final |0 converts the unsigned value into a signed value | |
return (al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0; | |
} : | |
Math.imul); | |
var stringRepeat = (typeof String.prototype.repeat === "function" && "x".repeat(3) === "xxx" ? | |
function (x, y) { | |
return x.repeat(y); | |
} : function (pattern, count) { | |
var result = ""; | |
while (count > 0) { | |
if (count & 1) { | |
result += pattern; | |
} | |
count >>= 1; | |
pattern += pattern; | |
} | |
return result; | |
}); | |
function Random(engine) { | |
if (!(this instanceof Random)) { | |
return new Random(engine); | |
} | |
if (engine == null) { | |
engine = Random.engines.nativeMath; | |
} else if (typeof engine !== "function") { | |
throw new TypeError("Expected engine to be a function, got " + typeof engine); | |
} | |
this.engine = engine; | |
} | |
var proto = Random.prototype; | |
Random.engines = { | |
nativeMath: function () { | |
return (Math.random() * 0x100000000) | 0; | |
}, | |
mt19937: (function (Int32Array) { | |
// http://en.wikipedia.org/wiki/Mersenne_twister | |
function refreshData(data) { | |
var k = 0; | |
var tmp = 0; | |
for (; | |
(k | 0) < 227; k = (k + 1) | 0) { | |
tmp = (data[k] & 0x80000000) | (data[(k + 1) | 0] & 0x7fffffff); | |
data[k] = data[(k + 397) | 0] ^ (tmp >>> 1) ^ ((tmp & 0x1) ? 0x9908b0df : 0); | |
} | |
for (; | |
(k | 0) < 623; k = (k + 1) | 0) { | |
tmp = (data[k] & 0x80000000) | (data[(k + 1) | 0] & 0x7fffffff); | |
data[k] = data[(k - 227) | 0] ^ (tmp >>> 1) ^ ((tmp & 0x1) ? 0x9908b0df : 0); | |
} | |
tmp = (data[623] & 0x80000000) | (data[0] & 0x7fffffff); | |
data[623] = data[396] ^ (tmp >>> 1) ^ ((tmp & 0x1) ? 0x9908b0df : 0); | |
} | |
function temper(value) { | |
value ^= value >>> 11; | |
value ^= (value << 7) & 0x9d2c5680; | |
value ^= (value << 15) & 0xefc60000; | |
return value ^ (value >>> 18); | |
} | |
function seedWithArray(data, source) { | |
var i = 1; | |
var j = 0; | |
var sourceLength = source.length; | |
var k = Math.max(sourceLength, 624) | 0; | |
var previous = data[0] | 0; | |
for (; | |
(k | 0) > 0; --k) { | |
data[i] = previous = ((data[i] ^ imul((previous ^ (previous >>> 30)), 0x0019660d)) + (source[j] | 0) + (j | 0)) | 0; | |
i = (i + 1) | 0; | |
++j; | |
if ((i | 0) > 623) { | |
data[0] = data[623]; | |
i = 1; | |
} | |
if (j >= sourceLength) { | |
j = 0; | |
} | |
} | |
for (k = 623; | |
(k | 0) > 0; --k) { | |
data[i] = previous = ((data[i] ^ imul((previous ^ (previous >>> 30)), 0x5d588b65)) - i) | 0; | |
i = (i + 1) | 0; | |
if ((i | 0) > 623) { | |
data[0] = data[623]; | |
i = 1; | |
} | |
} | |
data[0] = 0x80000000; | |
} | |
function mt19937() { | |
var data = new Int32Array(624); | |
var index = 0; | |
var uses = 0; | |
function next() { | |
if ((index | 0) >= 624) { | |
refreshData(data); | |
index = 0; | |
} | |
var value = data[index]; | |
index = (index + 1) | 0; | |
uses += 1; | |
return temper(value) | 0; | |
} | |
next.getUseCount = function() { | |
return uses; | |
}; | |
next.discard = function (count) { | |
uses += count; | |
if ((index | 0) >= 624) { | |
refreshData(data); | |
index = 0; | |
} | |
while ((count - index) > 624) { | |
count -= 624 - index; | |
refreshData(data); | |
index = 0; | |
} | |
index = (index + count) | 0; | |
return next; | |
}; | |
next.seed = function (initial) { | |
var previous = 0; | |
data[0] = previous = initial | 0; | |
for (var i = 1; i < 624; i = (i + 1) | 0) { | |
data[i] = previous = (imul((previous ^ (previous >>> 30)), 0x6c078965) + i) | 0; | |
} | |
index = 624; | |
uses = 0; | |
return next; | |
}; | |
next.seedWithArray = function (source) { | |
next.seed(0x012bd6aa); | |
seedWithArray(data, source); | |
return next; | |
}; | |
next.autoSeed = function () { | |
return next.seedWithArray(Random.generateEntropyArray()); | |
}; | |
return next; | |
} | |
return mt19937; | |
}(typeof Int32Array === "function" ? Int32Array : Array)), | |
browserCrypto: (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function" && typeof Int32Array === "function") ? (function () { | |
var data = null; | |
var index = 128; | |
return function () { | |
if (index >= 128) { | |
if (data === null) { | |
data = new Int32Array(128); | |
} | |
crypto.getRandomValues(data); | |
index = 0; | |
} | |
return data[index++] | 0; | |
}; | |
}()) : null | |
}; | |
Random.generateEntropyArray = function () { | |
var array = []; | |
var engine = Random.engines.nativeMath; | |
for (var i = 0; i < 16; ++i) { | |
array[i] = engine() | 0; | |
} | |
array.push(new Date().getTime() | 0); | |
return array; | |
}; | |
function returnValue(value) { | |
return function () { | |
return value; | |
}; | |
} | |
// [-0x80000000, 0x7fffffff] | |
Random.int32 = function (engine) { | |
return engine() | 0; | |
}; | |
proto.int32 = function () { | |
return Random.int32(this.engine); | |
}; | |
// [0, 0xffffffff] | |
Random.uint32 = function (engine) { | |
return engine() >>> 0; | |
}; | |
proto.uint32 = function () { | |
return Random.uint32(this.engine); | |
}; | |
// [0, 0x1fffffffffffff] | |
Random.uint53 = function (engine) { | |
var high = engine() & 0x1fffff; | |
var low = engine() >>> 0; | |
return (high * 0x100000000) + low; | |
}; | |
proto.uint53 = function () { | |
return Random.uint53(this.engine); | |
}; | |
// [0, 0x20000000000000] | |
Random.uint53Full = function (engine) { | |
while (true) { | |
var high = engine() | 0; | |
if (high & 0x200000) { | |
if ((high & 0x3fffff) === 0x200000 && (engine() | 0) === 0) { | |
return 0x20000000000000; | |
} | |
} else { | |
var low = engine() >>> 0; | |
return ((high & 0x1fffff) * 0x100000000) + low; | |
} | |
} | |
}; | |
proto.uint53Full = function () { | |
return Random.uint53Full(this.engine); | |
}; | |
// [-0x20000000000000, 0x1fffffffffffff] | |
Random.int53 = function (engine) { | |
var high = engine() | 0; | |
var low = engine() >>> 0; | |
return ((high & 0x1fffff) * 0x100000000) + low + (high & 0x200000 ? -0x20000000000000 : 0); | |
}; | |
proto.int53 = function () { | |
return Random.int53(this.engine); | |
}; | |
// [-0x20000000000000, 0x20000000000000] | |
Random.int53Full = function (engine) { | |
while (true) { | |
var high = engine() | 0; | |
if (high & 0x400000) { | |
if ((high & 0x7fffff) === 0x400000 && (engine() | 0) === 0) { | |
return 0x20000000000000; | |
} | |
} else { | |
var low = engine() >>> 0; | |
return ((high & 0x1fffff) * 0x100000000) + low + (high & 0x200000 ? -0x20000000000000 : 0); | |
} | |
} | |
}; | |
proto.int53Full = function () { | |
return Random.int53Full(this.engine); | |
}; | |
function add(generate, addend) { | |
if (addend === 0) { | |
return generate; | |
} else { | |
return function (engine) { | |
return generate(engine) + addend; | |
}; | |
} | |
} | |
Random.integer = (function () { | |
function isPowerOfTwoMinusOne(value) { | |
return ((value + 1) & value) === 0; | |
} | |
function bitmask(masking) { | |
return function (engine) { | |
return engine() & masking; | |
}; | |
} | |
function downscaleToLoopCheckedRange(range) { | |
var extendedRange = range + 1; | |
var maximum = extendedRange * Math.floor(0x100000000 / extendedRange); | |
return function (engine) { | |
var value = 0; | |
do { | |
value = engine() >>> 0; | |
} while (value >= maximum); | |
return value % extendedRange; | |
}; | |
} | |
function downscaleToRange(range) { | |
if (isPowerOfTwoMinusOne(range)) { | |
return bitmask(range); | |
} else { | |
return downscaleToLoopCheckedRange(range); | |
} | |
} | |
function isEvenlyDivisibleByMaxInt32(value) { | |
return (value | 0) === 0; | |
} | |
function upscaleWithHighMasking(masking) { | |
return function (engine) { | |
var high = engine() & masking; | |
var low = engine() >>> 0; | |
return (high * 0x100000000) + low; | |
}; | |
} | |
function upscaleToLoopCheckedRange(extendedRange) { | |
var maximum = extendedRange * Math.floor(0x20000000000000 / extendedRange); | |
return function (engine) { | |
var ret = 0; | |
do { | |
var high = engine() & 0x1fffff; | |
var low = engine() >>> 0; | |
ret = (high * 0x100000000) + low; | |
} while (ret >= maximum); | |
return ret % extendedRange; | |
}; | |
} | |
function upscaleWithinU53(range) { | |
var extendedRange = range + 1; | |
if (isEvenlyDivisibleByMaxInt32(extendedRange)) { | |
var highRange = ((extendedRange / 0x100000000) | 0) - 1; | |
if (isPowerOfTwoMinusOne(highRange)) { | |
return upscaleWithHighMasking(highRange); | |
} | |
} | |
return upscaleToLoopCheckedRange(extendedRange); | |
} | |
function upscaleWithinI53AndLoopCheck(min, max) { | |
return function (engine) { | |
var ret = 0; | |
do { | |
var high = engine() | 0; | |
var low = engine() >>> 0; | |
ret = ((high & 0x1fffff) * 0x100000000) + low + (high & 0x200000 ? -0x20000000000000 : 0); | |
} while (ret < min || ret > max); | |
return ret; | |
}; | |
} | |
return function (min, max) { | |
min = Math.floor(min); | |
max = Math.floor(max); | |
if (min < -0x20000000000000 || !isFinite(min)) { | |
throw new RangeError("Expected min to be at least " + (-0x20000000000000)); | |
} else if (max > 0x20000000000000 || !isFinite(max)) { | |
throw new RangeError("Expected max to be at most " + 0x20000000000000); | |
} | |
var range = max - min; | |
if (range <= 0 || !isFinite(range)) { | |
return returnValue(min); | |
} else if (range === 0xffffffff) { | |
if (min === 0) { | |
return Random.uint32; | |
} else { | |
return add(Random.int32, min + 0x80000000); | |
} | |
} else if (range < 0xffffffff) { | |
return add(downscaleToRange(range), min); | |
} else if (range === 0x1fffffffffffff) { | |
return add(Random.uint53, min); | |
} else if (range < 0x1fffffffffffff) { | |
return add(upscaleWithinU53(range), min); | |
} else if (max - 1 - min === 0x1fffffffffffff) { | |
return add(Random.uint53Full, min); | |
} else if (min === -0x20000000000000 && max === 0x20000000000000) { | |
return Random.int53Full; | |
} else if (min === -0x20000000000000 && max === 0x1fffffffffffff) { | |
return Random.int53; | |
} else if (min === -0x1fffffffffffff && max === 0x20000000000000) { | |
return add(Random.int53, 1); | |
} else if (max === 0x20000000000000) { | |
return add(upscaleWithinI53AndLoopCheck(min - 1, max - 1), 1); | |
} else { | |
return upscaleWithinI53AndLoopCheck(min, max); | |
} | |
}; | |
}()); | |
proto.integer = function (min, max) { | |
return Random.integer(min, max)(this.engine); | |
}; | |
// [0, 1] (floating point) | |
Random.realZeroToOneInclusive = function (engine) { | |
return Random.uint53Full(engine) / 0x20000000000000; | |
}; | |
proto.realZeroToOneInclusive = function () { | |
return Random.realZeroToOneInclusive(this.engine); | |
}; | |
// [0, 1) (floating point) | |
Random.realZeroToOneExclusive = function (engine) { | |
return Random.uint53(engine) / 0x20000000000000; | |
}; | |
proto.realZeroToOneExclusive = function () { | |
return Random.realZeroToOneExclusive(this.engine); | |
}; | |
Random.real = (function () { | |
function multiply(generate, multiplier) { | |
if (multiplier === 1) { | |
return generate; | |
} else if (multiplier === 0) { | |
return function () { | |
return 0; | |
}; | |
} else { | |
return function (engine) { | |
return generate(engine) * multiplier; | |
}; | |
} | |
} | |
return function (left, right, inclusive) { | |
if (!isFinite(left)) { | |
throw new RangeError("Expected left to be a finite number"); | |
} else if (!isFinite(right)) { | |
throw new RangeError("Expected right to be a finite number"); | |
} | |
return add( | |
multiply( | |
inclusive ? Random.realZeroToOneInclusive : Random.realZeroToOneExclusive, | |
right - left), | |
left); | |
}; | |
}()); | |
proto.real = function (min, max, inclusive) { | |
return Random.real(min, max, inclusive)(this.engine); | |
}; | |
Random.bool = (function () { | |
function isLeastBitTrue(engine) { | |
return (engine() & 1) === 1; | |
} | |
function lessThan(generate, value) { | |
return function (engine) { | |
return generate(engine) < value; | |
}; | |
} | |
function probability(percentage) { | |
if (percentage <= 0) { | |
return returnValue(false); | |
} else if (percentage >= 1) { | |
return returnValue(true); | |
} else { | |
var scaled = percentage * 0x100000000; | |
if (scaled % 1 === 0) { | |
return lessThan(Random.int32, (scaled - 0x80000000) | 0); | |
} else { | |
return lessThan(Random.uint53, Math.round(percentage * 0x20000000000000)); | |
} | |
} | |
} | |
return function (numerator, denominator) { | |
if (denominator == null) { | |
if (numerator == null) { | |
return isLeastBitTrue; | |
} | |
return probability(numerator); | |
} else { | |
if (numerator <= 0) { | |
return returnValue(false); | |
} else if (numerator >= denominator) { | |
return returnValue(true); | |
} | |
return lessThan(Random.integer(0, denominator - 1), numerator); | |
} | |
}; | |
}()); | |
proto.bool = function (numerator, denominator) { | |
return Random.bool(numerator, denominator)(this.engine); | |
}; | |
function toInteger(value) { | |
var number = +value; | |
if (number < 0) { | |
return Math.ceil(number); | |
} else { | |
return Math.floor(number); | |
} | |
} | |
function convertSliceArgument(value, length) { | |
if (value < 0) { | |
return Math.max(value + length, 0); | |
} else { | |
return Math.min(value, length); | |
} | |
} | |
Random.pick = function (engine, array, begin, end) { | |
var length = array.length; | |
var start = begin == null ? 0 : convertSliceArgument(toInteger(begin), length); | |
var finish = end === void 0 ? length : convertSliceArgument(toInteger(end), length); | |
if (start >= finish) { | |
return void 0; | |
} | |
var distribution = Random.integer(start, finish - 1); | |
return array[distribution(engine)]; | |
}; | |
proto.pick = function (array, begin, end) { | |
return Random.pick(this.engine, array, begin, end); | |
}; | |
function returnUndefined() { | |
return void 0; | |
} | |
var slice = Array.prototype.slice; | |
Random.picker = function (array, begin, end) { | |
var clone = slice.call(array, begin, end); | |
if (!clone.length) { | |
return returnUndefined; | |
} | |
var distribution = Random.integer(0, clone.length - 1); | |
return function (engine) { | |
return clone[distribution(engine)]; | |
}; | |
}; | |
Random.shuffle = function (engine, array, downTo) { | |
var length = array.length; | |
if (length) { | |
if (downTo == null) { | |
downTo = 0; | |
} | |
for (var i = (length - 1) >>> 0; i > downTo; --i) { | |
var distribution = Random.integer(0, i); | |
var j = distribution(engine); | |
if (i !== j) { | |
var tmp = array[i]; | |
array[i] = array[j]; | |
array[j] = tmp; | |
} | |
} | |
} | |
return array; | |
}; | |
proto.shuffle = function (array) { | |
return Random.shuffle(this.engine, array); | |
}; | |
Random.sample = function (engine, population, sampleSize) { | |
if (sampleSize < 0 || sampleSize > population.length || !isFinite(sampleSize)) { | |
throw new RangeError("Expected sampleSize to be within 0 and the length of the population"); | |
} | |
if (sampleSize === 0) { | |
return []; | |
} | |
var clone = slice.call(population); | |
var length = clone.length; | |
if (length === sampleSize) { | |
return Random.shuffle(engine, clone, 0); | |
} | |
var tailLength = length - sampleSize; | |
return Random.shuffle(engine, clone, tailLength - 1).slice(tailLength); | |
}; | |
proto.sample = function (population, sampleSize) { | |
return Random.sample(this.engine, population, sampleSize); | |
}; | |
Random.die = function (sideCount) { | |
return Random.integer(1, sideCount); | |
}; | |
proto.die = function (sideCount) { | |
return Random.die(sideCount)(this.engine); | |
}; | |
Random.dice = function (sideCount, dieCount) { | |
var distribution = Random.die(sideCount); | |
return function (engine) { | |
var result = []; | |
result.length = dieCount; | |
for (var i = 0; i < dieCount; ++i) { | |
result[i] = distribution(engine); | |
} | |
return result; | |
}; | |
}; | |
proto.dice = function (sideCount, dieCount) { | |
return Random.dice(sideCount, dieCount)(this.engine); | |
}; | |
// http://en.wikipedia.org/wiki/Universally_unique_identifier | |
Random.uuid4 = (function () { | |
function zeroPad(string, zeroCount) { | |
return stringRepeat("0", zeroCount - string.length) + string; | |
} | |
return function (engine) { | |
var a = engine() >>> 0; | |
var b = engine() | 0; | |
var c = engine() | 0; | |
var d = engine() >>> 0; | |
return ( | |
zeroPad(a.toString(16), 8) + | |
"-" + | |
zeroPad((b & 0xffff).toString(16), 4) + | |
"-" + | |
zeroPad((((b >> 4) & 0x0fff) | 0x4000).toString(16), 4) + | |
"-" + | |
zeroPad(((c & 0x3fff) | 0x8000).toString(16), 4) + | |
"-" + | |
zeroPad(((c >> 4) & 0xffff).toString(16), 4) + | |
zeroPad(d.toString(16), 8)); | |
}; | |
}()); | |
proto.uuid4 = function () { | |
return Random.uuid4(this.engine); | |
}; | |
Random.string = (function () { | |
// has 2**x chars, for faster uniform distribution | |
var DEFAULT_STRING_POOL = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; | |
return function (pool) { | |
if (pool == null) { | |
pool = DEFAULT_STRING_POOL; | |
} | |
var length = pool.length; | |
if (!length) { | |
throw new Error("Expected pool not to be an empty string"); | |
} | |
var distribution = Random.integer(0, length - 1); | |
return function (engine, length) { | |
var result = ""; | |
for (var i = 0; i < length; ++i) { | |
var j = distribution(engine); | |
result += pool.charAt(j); | |
} | |
return result; | |
}; | |
}; | |
}()); | |
proto.string = function (length, pool) { | |
return Random.string(pool)(this.engine, length); | |
}; | |
Random.hex = (function () { | |
var LOWER_HEX_POOL = "0123456789abcdef"; | |
var lowerHex = Random.string(LOWER_HEX_POOL); | |
var upperHex = Random.string(LOWER_HEX_POOL.toUpperCase()); | |
return function (upper) { | |
if (upper) { | |
return upperHex; | |
} else { | |
return lowerHex; | |
} | |
}; | |
}()); | |
proto.hex = function (length, upper) { | |
return Random.hex(upper)(this.engine, length); | |
}; | |
Random.date = function (start, end) { | |
if (!(start instanceof Date)) { | |
throw new TypeError("Expected start to be a Date, got " + typeof start); | |
} else if (!(end instanceof Date)) { | |
throw new TypeError("Expected end to be a Date, got " + typeof end); | |
} | |
var distribution = Random.integer(start.getTime(), end.getTime()); | |
return function (engine) { | |
return new Date(distribution(engine)); | |
}; | |
}; | |
proto.date = function (start, end) { | |
return Random.date(start, end)(this.engine); | |
}; | |
if (typeof define === "function" && define.amd) { | |
define(function () { | |
return Random; | |
}); | |
} else if (typeof module !== "undefined" && typeof require === "function") { | |
module.exports = Random; | |
} else { | |
(function () { | |
var oldGlobal = root[GLOBAL_KEY]; | |
Random.noConflict = function () { | |
root[GLOBAL_KEY] = oldGlobal; | |
return this; | |
}; | |
}()); | |
root[GLOBAL_KEY] = Random; | |
} | |
}(this)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment