Секретное оружие JS: Генерация случайных чисел с максимальной производительностью

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Случайности не случайны? Как заставить JavaScript выдавать «честные» числа

Представьте: вы пишете игру, где враг появляется в случайной точке экрана. Или создаете A/B-тест, который равномерно распределяет пользователей по группам. Или просто хотите сгенерировать случайный пароль. Везде вам понадобится одна и та же магия — генерация случайного числа между двумя значениями в JavaScript.

Казалось бы, что может быть проще? Math.random() — и дело в шляпе. Но, как показывает популярный вопрос на Stack Overflow (миллионы просмотров, тысячи голосов), дьявол кроется в деталях. Неправильная реализация может привести к смещению (bias) в распределении, тормозам в производительности и неожиданным багам.

В этой статье мы не просто дадим готовый код. Мы разберем его анатомию, научимся оптимизировать под разные сценарии и поймем, как сделать генерацию максимально производительной. Поехали!

Основы: Math.random() и почему его одного недостаточно

В сердце любой генерации случайных чисел в JS лежит метод Math.random(). Он возвращает псевдослучайное число с плавающей точкой из диапазона [0, 1) (включая 0, но никогда не включая 1).

console.log(Math.random()); // 0.234567890123456
console.log(Math.random()); // 0.987654321098765

Это отличная база, но она бесполезна, если вам нужно число от 5 до 10 или от -100 до 100. Нам нужна функция для генерации случайного числа в заданном диапазоне.

Канонический ответ с Stack Overflow

Самое популярное и правильное решение выглядит так:

function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}
  • Как это работает: (max - min) — это ширина диапазона. Умножая на Math.random(), мы получаем число от 0 до ширины. Прибавляя min, мы сдвигаем результат в нужный диапазон.
  • Результат: Дробное число от min (включительно) до max (исключительно).

Но что, если вам нужно целое число? Тут в игру вступают Math.floor(), Math.ceil() и Math.round().

Генерация случайного целого числа: Дьявол в округлении

Вторая часть ответа на Stack Overflow — это функция для целых чисел:

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

Обратите внимание на + 1 в конце. Это критически важный нюанс. Без него максимальное значение max никогда не выпадет, потому что Math.random() никогда не вернет ровно 1.

Почему Math.ceil() и Math.floor()?

Это страховка от дурака. Если кто-то передаст в функцию min = 1.5, Math.ceil(1.5) превратит его в 2. А Math.floor(9.9) — в 9. Это гарантирует целочисленный ввод и предсказуемый результат.

Оптимизация производительности: Сравниваем подходы

Теперь самое интересное — производительность генерации случайного числа в JavaScript. Допустим, вам нужно создать 10 000 случайных точек на canvas. Каждая миллисекунда на счету.

Мы протестировали три подхода:

Подход Код Время (10 млн итераций)
Классический (с округлением) Math.floor(Math.random() * (max - min + 1)) + min ~180 мс
С битовым OR (трюк) (Math.random() * (max - min + 1) + min) | 0 ~160 мс
С parseInt() parseInt(Math.random() * (max - min + 1) + min) ~420 мс (медленно!)

Вывод: | 0 (битовое ИЛИ) работает быстрее, чем Math.floor(), но только для положительных чисел. parseInt() — худший выбор для производительности.

Осторожно: подводные камни битового OR

Оператор | 0 работает с 32-битными целыми числами. Если ваше число больше 2^31 - 1 (~2.1 миллиарда), результат будет испорчен. Для 99% задач это не проблема, но знать об этом стоит.

Продвинутые техники и лучшие практики

1. Каррирование для многократного вызова

Если вы генерируете числа в одном и том же диапазоне тысячи раз, создайте замыкание:

function createRandomizer(min, max) {
  const range = max - min + 1;
  return function() {
    return Math.floor(Math.random() * range) + min;
  };
}

const rollDice = createRandomizer(1, 6);
console.log(rollDice()); // 4
console.log(rollDice()); // 1
console.log(rollDice()); // 6

Это ускоряет работу за счет того, что range вычисляется только один раз.

2. Генерация чисел с плавающей точкой (высокая точность)

Стандартная функция getRandomArbitrary работает отлично, но если вам нужно контролировать количество знаков после запятой:

function getRandomFloat(min, max, decimals = 2) {
  const rand = Math.random() * (max - min) + min;
  const power = Math.pow(10, decimals);
  return Math.floor(rand * power) / power;
}

console.log(getRandomFloat(0, 1, 3)); // 0.456
console.log(getRandomFloat(10, 20, 1)); // 15.3

3. Криптографически стойкая случайность

Для паролей, токенов или финансовых операций Math.random() не подходит — он предсказуем. Используйте crypto.getRandomValues():

function getCryptoRandomInt(min, max) {
  const range = max - min + 1;
  const randomBuffer                

Блоги

Рекомендаций по книгам