Skip to content

Instantly share code, notes, and snippets.

@westc
Last active February 23, 2026 15:19
Show Gist options
  • Select an option

  • Save westc/3499b073e3903da79c6a8a692f5d7b54 to your computer and use it in GitHub Desktop.

Select an option

Save westc/3499b073e3903da79c6a8a692f5d7b54 to your computer and use it in GitHub Desktop.
A simple & complete implementation of random which can also generate cryptographically sound numbers.
var random = (() => {
const uints = new Uint32Array(2);
/**
* @typedef $randomOptions
* @property {boolean} [returnInt=false]
* Defaults to `false`. If `true` then the returned number will be an
* integer. Floats will be rounded down if `includedLimit` is less than
* `excludedLimit`, otherwise they will be rounded up.
* @property {boolean} [useCrypto=false]
* Defaults to `false`. If `true` then the returned number will be
* cryptographically generated.
*/
/**
* Generates a random number in the given range.
* @param {number} includedLimit
* A boundary number of the range that can be returned. This can be
* greater than or less than `excludedLimit`.
* @param {number} excludedLimit
* A number just outside of the range of numbers that can be returned.
* This can be greater than or less than `includedLimit`.
* @param {$randomOptions=} options
* Optional. Additional options that can be applied when generating the
* random number.
* @returns {number}
* A number in the range of `includedLimit` to `excludedLimit` where
* `includedLimit` is a possible return value but `excludedLimit` isn't.
*/
return function random(includedLimit, excludedLimit, options) {
const {returnInt = false, useCrypto = false} = options ?? {};
// If the return value should be an integer but the includedLimit is not an
// integer then either turn it into an integer or throw an error if there is
// no integer between includedLimit and excludedLimit.
const asc = includedLimit < excludedLimit;
let [min, max] = asc ? [includedLimit, excludedLimit] : [excludedLimit, includedLimit];
if (returnInt) {
[min, max] = [Math.ceil(min), Math.floor(max)];
if (min > max) {
throw new Error(`There is no integer between ${includedLimit} and ${excludedLimit}.`);
}
includedLimit = asc ? Math.ceil(includedLimit) : Math.floor(includedLimit);
}
// Dont allow min and max to be the same.
if (min === max) {
throw new Error(`There is are no numbers greater than or equal to ${min} but less than ${max}.`);
}
// Generate a random number.
let randomNumber;
if (useCrypto) {
// Generate 2 cryptographically random 32-bit positive integers and turn
// them into 1 53-bit positive integer and then divide that 53-bit
// positive integer by the first 54-bit positive integer to emulate
// Math.random() while ensuring that it is cryptographically sound. We do
// this by taking the first 32-bit random number and then adding 21 bits
// from the second number.
// 0x200000 = 2**(32 - 11)
// 0x20000000000000 = 2**53 (first 54-bit positive integer)
crypto.getRandomValues(uints);
randomNumber = (uints[0] * 0x200000 + (uints[1] >>> 11)) / 0x20000000000000;
}
else {
randomNumber = Math.random();
}
// Return the random number in the specified range either as an int or a
// float depending on if returnInt was specified as true-ish.
let offset = randomNumber * (max - min);
if (returnInt) {
offset = Math.floor(offset);
}
return asc
? includedLimit + offset
: (includedLimit - offset);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment