Last active
February 23, 2026 15:19
-
-
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.
This file contains hidden or 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 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