Генерация случайных чисел в JavaScript

Генерация случайных чисел в JavaScript Полезное

Генерация случайных чисел в JavaScript — это получение непредсказуемого значения программным способом, чаще всего через встроенный метод Math.random(). Он подходит для игр, анимации, перемешивания списков и выбора случайного элемента, но не годится для паролей, токенов и ключей: там нужен криптостойкий генератор crypto.getRandomValues().

Ниже разберу все рабочие приемы по порядку: число в диапазоне [0, 1), дробное и целое число в произвольном диапазоне [min, max], случайный элемент массива и криптостойкую генерацию. Затем — типичные ошибки. Весь код прогнан в Node.js 22; границы диапазонов проверены перебором.

Math.random(): число в диапазоне [0, 1)

Math.random() возвращает псевдослучайное число с плавающей точкой в полуинтервале [0, 1): ноль в результат попасть может, а единица — нет, верхняя граница строго исключена. Аргументов метод не принимает.

console.log(Math.random());
// например: 0.5097055147211202
// при каждом вызове число другое, всегда 0 <= x < 1

Проверить границы можно перебором: за миллион вызовов максимум остается меньше единицы, а минимум почти достигает нуля.

let min = 1, max = 0;
for (let i = 0; i < 1_000_000; i++) {
  const r = Math.random();
  if (r < min) min = r;
  if (r > max) max = r;
}
console.log('максимум строго меньше 1:', max < 1); // true

Само по себе число из [0, 1) полезно редко — обычно его масштабируют под нужный диапазон.

Случайное дробное число в диапазоне [min, max)

Чтобы получить дробное число в диапазоне, результат Math.random() растягивают на ширину max - min и сдвигают на min. Нижняя граница входит в диапазон, верхняя — нет (наследуется от Math.random()).

function randomFloat(min, max) {
  return Math.random() * (max - min) + min;
}
console.log(randomFloat(5, 10)); // например: 7.34..., всегда 5 <= x < 10

Формула работает и с отрицательными границами: randomFloat(-1, 1) даст число от -1 (включительно) почти до 1.

Случайное целое число в диапазоне [min, max]

Это частый сценарий: бросок кубика, номер вопроса, индекс. Здесь нужны обе границы включительно, поэтому к ширине диапазона прибавляют 1, а дробную часть отбрасывают через Math.floor:

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 6)); // целое от 1 до 6 включительно (бросок кубика)

Разберу, почему именно + 1. Math.random() дает число из [0, 1). Умножение на (max - min + 1) растягивает его до полуинтервала [0, max — min + 1), а Math.floor округляет вниз до целого — получаются значения от 0 до max - min включительно. Прибавляем min и накрываем весь диапазон, вместе с верхней границей.

Что обе границы реально достижимы, легко проверить перебором:

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
let lo = Infinity, hi = -Infinity;
for (let i = 0; i < 1_000_000; i++) {
  const v = randomInt(1, 6);
  if (v < lo) lo = v;
  if (v > hi) hi = v;
}
console.log('минимум:', lo, 'максимум:', hi); // минимум: 1 максимум: 6

Важно: min и max должны быть целыми. Если передать дробные, Math.floor даст смещенные границы. Частный случай — монетка (0 или 1): Math.floor(Math.random() * 2).

Случайный элемент массива

Отдельная функция не нужна: берем случайный целый индекс от 0 до length - 1 и обращаемся по нему. Длина массива подставляется прямо в формулу, + 1 здесь не нужен, потому что индексация с нуля и последний индекс как раз length - 1.

const colors = ['красный', 'зеленый', 'синий'];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
console.log(randomColor); // один из трех элементов массива

Так же выбирают случайный вопрос из списка, карту из колоды или реплику бота. Для полного перемешивания массива (а не выбора одного элемента) используют алгоритм Фишера-Йейтса — это отдельная задача.

Криптостойкие случайные числа: crypto.getRandomValues

Math.random() — это псевдослучайный генератор (PRNG). Его выход статистически ровный, но предсказуемый: зная состояние генератора, последовательность можно восстановить. Поэтому для паролей, токенов сессии, ключей и любых значений, влияющих на безопасность, Math.random() использовать нельзя.

Для таких задач есть Web Crypto API и метод crypto.getRandomValues(). Он заполняет типизированный массив криптостойкими случайными числами. Метод доступен в браузерах и в Node.js (глобальный объект crypto, начиная с версии 18).

const buffer = new Uint32Array(1);
crypto.getRandomValues(buffer);
console.log(buffer[0]); // целое от 0 до 4294967295

Тип массива задает диапазон значений каждого элемента: Uint8Array — байты 0..255, Uint16Array — 0..65535, Uint32Array — 0..4294967295 (есть и знаковые Int8Array, Int16Array, Int32Array). Криптостойкое целое в нужном диапазоне получают так:

function secureRandomInt(min, max) {
  const range = max - min + 1;
  const buf = new Uint32Array(1);
  crypto.getRandomValues(buf);
  return min + (buf[0] % range);
}
console.log(secureRandomInt(1, 6)); // криптостойкое целое от 1 до 6

Оговорка про границу: остаток от деления (%) вносит крохотный перекос, если range не делит 2^32 нацело. Для маленьких диапазонов он пренебрежимо мал, но для строгой равномерности применяют выборку с отбрасыванием (rejection sampling) — отбрасывают значения из «хвоста», который не делится на диапазон ровно. Для готового идентификатора удобнее crypto.randomUUID():

console.log(crypto.randomUUID()); // например: cdb9a702-9ffd-4ee1-b3f5-4b0f1b10f74d

Типичные ошибки

Забытый + 1 в формуле целого. Без него верхняя граница не достигается — max не выпадает никогда:

function randomIntWrong(min, max) {
  return Math.floor(Math.random() * (max - min)) + min; // нет + 1
}
let hi = -Infinity;
for (let i = 0; i < 1_000_000; i++) {
  const v = randomIntWrong(1, 6);
  if (v > hi) hi = v;
}
console.log('максимум:', hi); // максимум: 5 - число 6 не появляется никогда

Исправление — вернуть + 1: Math.floor(Math.random() * (max - min + 1)) + min.

Math.round вместо Math.floor. Округление к ближайшему дает неравномерность: крайние значения выпадают вдвое реже, потому что к ним округляется только половина интервала.

const counts = {};
for (let i = 0; i < 3_000_000; i++) {
  const v = Math.round(Math.random() * (3 - 1)) + 1; // 1..3 через round
  counts[v] = (counts[v] || 0) + 1;
}
console.log(counts); // примерно { '1': 25%, '2': 50%, '3': 25% } - края реже

Исправление — всегда Math.floor с формулой (max - min + 1), которая дает равномерное распределение.

Math.random() для паролей и токенов. Псевдослучайный генератор предсказуем, поэтому такие значения уязвимы. Для всего, что связано с безопасностью, берут crypto.getRandomValues() или crypto.randomUUID().

Ожидание, что выпадет ровно max у дробной формулы. У randomFloat(min, max) верхняя граница исключена: значение может подойти к max вплотную, но не совпасть с ним. Если верхняя граница нужна включительно, работайте с целыми через randomInt.

FAQ

Можно ли задать сид (seed), чтобы Math.random() выдавал повторяемую последовательность? Нет, в стандартном JavaScript посева нет: результат нельзя воспроизвести между запусками. Если нужна повторяемость (например, для тестов), берут стороннюю библиотеку с сидом, например seedrandom.

Как округлить случайное число до нужного числа знаков после запятой? Метод .toFixed(2) вернет строку с двумя знаками; чтобы получить именно число, оберните результат в Number(...) или используйте Math.round(x * 100) / 100.

Чем crypto.getRandomValues() отличается от Math.random() по производительности? Криптостойкий генератор медленнее и работает с типизированными массивами, поэтому для игр и анимации оставляют Math.random(), а crypto подключают только там, где важна непредсказуемость.

Выводы

  • Math.random() возвращает дробное число в диапазоне [0, 1): ноль возможен, единица — нет.
  • Дробное число в диапазоне: Math.random() * (max - min) + min (верхняя граница исключена).
  • Целое число в диапазоне включительно: Math.floor(Math.random() * (max - min + 1)) + min — + 1 обязателен, иначе max не выпадет; Math.floor, а не Math.round, иначе края реже.
  • Случайный элемент массива: arr[Math.floor(Math.random() * arr.length)].
  • Для паролей, токенов и ключей Math.random() небезопасен — используйте crypto.getRandomValues() или crypto.randomUUID().

Где применяется на практике

Генерация случайных чисел нужна почти в любом веб-проекте: случайный баннер на странице, перемешивание вопросов в тесте, механика в браузерной игре, а на стороне безопасности — токены и коды подтверждения. Ключевое умение здесь — выбрать инструмент под задачу: Math.random() для игровой случайности и crypto там, где значение нельзя предсказать.

Освойте тему на практике

Разобраться с методами, массивами и работой с числами в JavaScript с нуля помогает курс JavaScript Basic: на нем разбирают синтаксис языка, встроенные объекты и практику на реальных задачах.

Освойте тему на практике

Посмотреть, как преподаватели объясняют такие темы вживую, и задать свои вопросы можно на открытых вебинарах Otus — они бесплатные и проходят регулярно.

Смежные темы: Условный оператор if в JavaScript, Случайные числа в Python, Массивы в программировании.

OTUS Журнал
Бесплатные открытые уроки (поп-ап)