← Download MyJS

MyJS block

random 1.0.0

Generates a random number between two limits.

Download with this
Exports
random
Works in
Browser and Node.js
Tags
random
The code of version 1.0.0
/**
 * @typedef RandomOptions
 * @property {boolean} [returnInt=false]
 *    If `true`, returns a whole number: one of the integers from
 *    `includedLimit` (rounded toward `excludedLimit` if it isn't whole) up to
 *    but not including `excludedLimit`.
 * @property {boolean} [useCrypto=false]
 *    If `true`, uses `crypto.getRandomValues()` instead of `Math.random()`, so
 *    the result can't be predicted.
 */

/**
 * Generates a random number between two limits. `includedLimit` can be
 * returned but `excludedLimit` never is. Either one can be the larger.
 * @param {number} includedLimit
 *    One end of the range, which can be returned.
 * @param {number} excludedLimit
 *    The other end of the range, which is never returned.
 * @param {RandomOptions} [options]
 * @returns {number}
 * @throws {RangeError}
 *    If a limit isn't a finite number, or there's no number (or, with
 *    `returnInt`, no integer) that can be returned.
 *
 * @example <caption>Rolling a die</caption>
 * random(1, 7, { returnInt: true });
 * // → 1, 2, 3, 4, 5 or 6
 *
 * @example <caption>A decimal number</caption>
 * random(0, 100);
 * // → a number like 42.81637 (never 100)
 *
 * @example <caption>Limits in either order</caption>
 * random(10, 0, { returnInt: true });
 * // → 1 to 10: 10 can be returned but 0 can't
 *
 * @example <caption>Limits that aren't whole numbers</caption>
 * random(0.5, 3.5, { returnInt: true });
 * // → 1, 2 or 3
 *
 * @example <caption>Picking from a list</caption>
 * const colors = ['red', 'green', 'blue'];
 * colors[random(0, colors.length, { returnInt: true })];
 * // → 'red', 'green' or 'blue'
 *
 * @example <caption>A code that can't be predicted</caption>
 * random(100000, 1000000, { returnInt: true, useCrypto: true });
 * // → a six-digit number like 583019
 */
function random(includedLimit, excludedLimit, options) {
  const { returnInt = false, useCrypto = false } = options ?? {};
  if (!Number.isFinite(includedLimit) || !Number.isFinite(excludedLimit)) {
    throw new RangeError('Both limits must be finite numbers.');
  }
  const asc = includedLimit < excludedLimit;

  let start = includedLimit;
  let size = Math.abs(excludedLimit - includedLimit);
  if (returnInt) {
    // The first and the just-out-of-range integers, counting from includedLimit
    start = asc ? Math.ceil(includedLimit) : Math.floor(includedLimit);
    const end = asc ? Math.ceil(excludedLimit) : Math.floor(excludedLimit);
    size = Math.abs(end - start);
  }
  if (!size) {
    throw new RangeError(`There's no ${returnInt ? 'integer' : 'number'} from ${includedLimit} up to but not including ${excludedLimit}.`);
  }

  for (;;) {
    let fraction;
    if (useCrypto) {
      // 32 bits from the first number and 21 from the second make a 53-bit
      // integer, which divided by 2**53 gives [0, 1) like Math.random()
      const uints = crypto.getRandomValues(new Uint32Array(2));
      fraction = (uints[0] * 0x200000 + (uints[1] >>> 11)) / 0x20000000000000;
    } else {
      fraction = Math.random();
    }
    let offset = fraction * size;
    if (returnInt) {
      offset = Math.floor(offset);
    }
    const result = asc ? start + offset : start - offset;
    // Rounding can land a float on excludedLimit, so pick again (very rare)
    if (result !== excludedLimit) {
      return result;
    }
  }
}

Versions

  1. 1.0.0 Oct 23, 2026

    Initial

JSDoc