Генерация случайных чисел в широком интервале на JavaScript

Задача генерации случайных чисел c равной вероятностью выпадания разных порядков в широком интервале не является тривиальной.

Предположим, необходимо получить целое число от 1 до 999999999999999 случайным образом, причем числа разных длин должны выпадать одинаково часто.

Это означает, что двух- и трехзначные числа, к примеру, должны выпадать так же часто, как и 14- и 15-значные — и так по каждому из порядков.

Первое решение, которое приходит в голову:

var rnd = Math.round(Math.random() * 999999999999999);

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

Исследование вероятности выпадания разных порядков генерируемых чисел

Напишем тест для подсчета статистики длины генерируемых чисел.

var maxNum = 999999999999999, // Максимальное число диапазона  
    maxNumLen = maxNum.toString().length, // Длина максимального числа
    lenCounter = [], // Массив подсчета длин получаемых чисел
    iterates = 5000; // Количество итераций

// Инициация массива длин (обнуление)
for(var j=1; j<=maxNumLen; j++) { lenCounter[j] = 0; }

/**
 * Генерируем случайное целое, 
 * подсчитываем его длину, 
 * увеличиваем соответствующий счетчик.
 */
for(var i = 0; i < iterates; i++) {  
	/* *** ТЕСТИРУЕМАЯ ФУНКЦИЯ *** */
    var newRnd = Math.round(Math.random() * maxNum);
    /* *** /ТЕСТИРУЕМАЯ ФУНКЦИЯ *** */
    
    var newRndLen = newRnd.toString().length;
    lenCounter[newRndLen]++;
}

// Вывод резульата
for(var k=1; k<=maxNumLen; k++) { 
	console.log("Length "+k+":", lenCounter[k]); 
}  

В результате исполнения кода получим:

Length 1: 0
Length 2: 0
Length 3: 0
Length 4: 0
Length 5: 0
Length 6: 0
Length 7: 0
Length 8: 0
Length 9: 0
Length 10: 0
Length 11: 1
Length 12: 5
Length 13: 40
Length 14: 481
Length 15: 4473

Видно, что преобладают 15-значные числа, а при каждом последующем уменьшении порядка шанс выпадания числа этой длины падает в 10 раз.

Правильная генерация случайных целых чисел с равной вероятностью выпадания разных порядков

Верный вариант кода для решения поставленной задачи:

/**
 * @param {number} maxNumLen Длина максимального числа
 */
function getRangeRnd(maxNumLen) {
	return Math.floor(Math.pow(10, Math.floor(Math.random() * maxNumLen + 1)) * Math.random())
}

Раскрывая от внутренних скобок к внешним, код можно прочитать так:

  • Генерируется целочисленная длина будущего числа: Math.floor(Math.random() * maxNumLen + 1) (добавляем единицу, так как Math.floor() округляет всегда в меньшую сторону);
  • Возводим число 10 в степень полученной длины;
  • Разбавляем шумом от Math.random();
  • Округляем финальный результат: нас интересуют только целые числа.

А для получения случайного числа в диапазоне длин с равномерным покрытием вероятности выпадания чисел разных порядков надо генерировать строку случайной длины, состоящей из случайных цифр [0,9].

Такой алгоритм можно применять при необходимости получать числа свыше 16-го порядка, так как это максимальный порядок для целого числа (согласно ECMA-262 #8.5).

Реализация функции генерации строки со случайным числом в диапазоне длин:

/**
 * @param {number} fromLen Начальная длина
 * @param {number} toLen Конечная длина
 * @returns {string} Результат генерации
 */
function getRangeRnd (fromLen, toLen) {
    var fullRandomStr = '';
    var lenRnd = getRandomBetween(fromLen, toLen);

    for (var j = 0; j < lenRnd; j++) {
        fullRandomStr += getRandomBetween(0, 9).toString();
    }
    return fullRandomStr;

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

Последовательность выполнения кода:

  1. Создаем пустую строку fullRandomStr;
  2. Генерируем ее будущую длину lenRnd;
  3. Заполняем в цикле случайными цифрами от 0 до 9.

Применив вышеописанную методику тестирования для обеих функций (представлено для первой, но справедливо и для второй), получаем покрытие:

Length 1: 362
Length 2: 329
Length 3: 362
Length 4: 324
Length 5: 329
Length 6: 324
Length 7: 334
Length 8: 337
Length 9: 350
Length 10: 331
Length 11: 301
Length 12: 356
Length 13: 354
Length 14: 315
Length 15: 292
Визуализация статистики покрытия
График распределения порядков случайных чисел

Получаем практически равное покрытие порядков.