
🎲 Як згенерувати випадкове ціле число в діапазоні на JavaScript
Випадкове число потрібне всюди. ID замовлення в інтернет-магазині, код скидання пароля, кидок кубика в браузерній грі, вибір переможця розіграшу. І майже завжди розробник пише той самий рядок: Math.random().
Але між «отримати випадкове дробове від 0 до 1» та «отримати випадкове ціле від 5 до 20» прірва з трьох операцій, у якій новачки регулярно помиляються: Math.floor і Math.ceil, плюс один чи не плюс, плутанина, яка дає або вихід за діапазон, або нерівномірний розподіл.
Розберемо формулу до останньої дужки, напишемо робочий генератор на чистому HTML+CSS+JS і зазирнемо туди, де Math.random() уже не справляється.
💡 Швидкий огляд:
- Розберіть формулу Math.floor(Math.random() * (max - min + 1)) + min, зрозумійте кожну операцію.
- Дізнайтеся, чому тільки Math.floor, а не Math.ceil чи Math.round, на прикладах спотворень діапазону.
- Скопіюйте готовий HTML-генератор: два поля, кнопка, виведення результату, чистий JS, без фреймворків.
- Опануйте Crypto.getRandomValues, коли Math.random не годиться і потрібна криптостійка альтернатива.
Як працює Math.random()
Math.random(), статичний метод вбудованого об'єкта Math. Він не приймає аргументів і повертає псевдовипадкове число з рухомою комою в діапазоні [0, 1), згідно зі специфікацією MDN. Квадратна дужка біля нуля означає «включно», кругла біля одиниці, «виключаючи». Тобто ви можете отримати, наприклад 0.0, значення близько 0.372 або близьке до 0.999, але ніколи не отримаєте рівно 1.0.
Розподіл приблизно рівномірний. Реалізація зашита в рушій (V8, SpiderMonkey, JavaScriptCore), і конкретний алгоритм (зазвичай xorshift128+) розробник не вибирає і не скидає, сід задається рушієм під час старту.
Сам по собі Math.random() для прикладних задач марний. Іграм потрібні цілі числа, лотереям, цілі в діапазоні, криптографії, криптостійка ентропія, якої в Math.random() немає. Тому поверх нього завжди накручують формулу приведення до діапазону.
Формула: випадкове ціле в діапазоні [min, max]
Базова формула, яка покриває переважну більшість випадків у веброзробці:
1 function randomInt(min, max) { 2 return Math.floor(Math.random() * (max - min + 1)) + min; 3 }
Розберемо на навчальному прикладі з min = 5 і max = 10, спираючись на документацію Math.random:
- Припустімо,
Math.random()повернув значенняRу діапазоні [0, 1). max - min + 1=10 - 5 + 1=6, кількість можливих цілих результатів: 5, 6, 7, 8, 9, 10.R * 6, масштабували випадкове дробове на ширину діапазону.Math.floor(R * 6), відкинули дробову частину вниз. Тепер у нас індекс від 0 до 5.индекс + min, зсунули наmin. Готово: випадкове ціле від 5 до 10 включно.
Та сама формула в однорядковому записі:
1 const roll = Math.floor(Math.random() * (max - min + 1)) + min;
Чому Math.floor, а не Math.ceil чи Math.round
Вибір методу округлення критично впливає на розподіл. Покажемо на навчальному прикладі з діапазоном від 1 до 3 (усі значення, крім меж інтервалів, розрахункові). Підхід описано в канонічній відповіді на StackOverflow.
З Math.floor і формулою (max - min + 1):
- Якщо
Math.random()потрапив у першу третину діапазону [0, 1): множення на 3 дасть значення від 0 до майже 1,Math.floorвідкине дробову частину, вийде індекс 0, плюсmin, випаде 1 (рівно третина випадків). - Якщо
Math.random()у середній третині: множення на 3 дасть від 1 до майже 2,Math.floor→ індекс 1, плюсmin→ 2 (ще третина). - Якщо
Math.random()в останній третині: множення на 3 дасть від 2 до майже 3,Math.floor→ індекс 2, плюсmin→ 3 (остання третина).
Рівномірно.
Що буде з Math.ceil? Math.ceil(Math.random() * 3) дасть 1, 2, 3, але в 1 шанс буде мізерно малий (тільки коли Math.random() рівно 0, що майже не трапляється). Крайні значення спотворені.
Math.round теж нерівномірний: крайні значення отримують половинний шанс відносно середніх. Тому в генерації випадкових цілих використовують винятково Math.floor.
Повний приклад: генератор на HTML, CSS і JavaScript
Робоча сторінка, яку можна зберегти як .html і відкрити в браузері. Два поля введення, кнопка, результат на зеленому тлі.
1 <!DOCTYPE html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Генератор случайных чисел</title> 6 <style> 7 body { 8 font-family: system-ui, sans-serif; 9 max-width: 400px; 10 margin: 50px auto; 11 padding: 0 16px; 12 } 13 label { 14 display: block; 15 margin-top: 12px; 16 font-weight: 600; 17 } 18 input { 19 width: 100%; 20 padding: 8px; 21 margin-top: 4px; 22 font-size: 16px; 23 box-sizing: border-box; 24 } 25 button { 26 margin-top: 16px; 27 padding: 10px 24px; 28 font-size: 16px; 29 cursor: pointer; 30 } 31 .result { 32 margin-top: 20px; 33 font-size: 24px; 34 font-weight: 700; 35 color: #2e7d32; 36 } 37 </style> 38 </head> 39 <body> 40 41 <h1>Случайное число в диапазоне</h1> 42 43 <label for="minInput">От:</label> 44 <input type="number" id="minInput" value="1"> 45 46 <label for="maxInput">До:</label> 47 <input type="number" id="maxInput" value="100"> 48 49 <button id="generateBtn">Сгенерировать</button> 50 51 <div class="result" id="resultDisplay"></div> 52 53 <script> 54 function randomInt(min, max) { 55 return Math.floor(Math.random() * (max - min + 1)) + min; 56 } 57 58 const minInput = document.getElementById('minInput'); 59 const maxInput = document.getElementById('maxInput'); 60 const generateBtn = document.getElementById('generateBtn'); 61 const resultDisplay = document.getElementById('resultDisplay'); 62 63 generateBtn.addEventListener('click', () => { 64 const min = parseInt(minInput.value, 10); 65 const max = parseInt(maxInput.value, 10); 66 67 if (isNaN(min) || isNaN(max)) { 68 resultDisplay.textContent = 'Введите оба числа'; 69 return; 70 } 71 if (min > max) { 72 resultDisplay.textContent = '«От» не может быть больше «До»'; 73 return; 74 } 75 76 const result = randomInt(min, max); 77 resultDisplay.textContent = `Результат: ${result}`; 78 }); 79 </script> 80 81 </body> 82 </html>
Код навмисно написаний на чистому JavaScript, без фреймворків і збирачів, так його можна скопіювати і запустити миттєво, без npm install. Функція randomInt приймає min і max, повертає ціле включно по обох кінцях. Обробник кнопки читає значення з полів, валідує і виводить результат.
Коли Math.random() недостатньо: Crypto.getRandomValues()
Math.random(), псевдовипадковий генератор. Його достатньо для ігор, анімацій, A/B-тестів і вибору випадкового елемента масиву. Але для задач, де передбачуваність означає вразливість, він не годиться: токени скидання пароля, ключі API, криптографічні nonce, генератори одноразових посилань.
Уявіть: ви надсилаєте користувачеві посилання для скидання пароля з токеном, згенерованим через Math.random(). Знаючи сід і алгоритм (а вони стандартні для рушія), зловмисник може відновити послідовність і підібрати токен. Саме тому будь-який код, що стосується безпеки, повинен використовувати криптостійке джерело випадковості.
Для таких сценаріїв існує Web Crypto API, crypto.getRandomValues(). Він використовує криптостійке джерело ентропії операційної системи і заповнює переданий типізований масив випадковими значеннями.
Генерація криптостійкого випадкового цілого в діапазоні:
1 function secureRandomInt(min, max) { 2 const range = max - min + 1; 3 const maxSafeVal = Math.floor(256 ** 4 / range) * range; 4 const buffer = new Uint32Array(1); 5 6 do { 7 crypto.getRandomValues(buffer); 8 } while (buffer[0] >= maxSafeVal); 9 10 return min + (buffer[0] % range); 11 }
Тут використовується rejection sampling: функція запитує випадкове 32-бітне значення і відкидає ті, що виходять за межі рівномірно подільного діапазону — це виключає зміщення (modulo bias), коли одні значення випадають частіше за інші.
На практиці для більшості вебзастосунків вистачає Math.random(). Але якщо ви пишете генератор одноразових посилань або кодів підтвердження, беріть crypto.getRandomValues().
Відео: генерація випадкового числа в JavaScript
Коротка демонстрація від каналу dcode, розбір Math.random() і формули діапазону з живим прикладом у консолі браузера:
⁉️🤔 Часті запитання
Чи включає формула Math.floor(Math.random() * (max - min + 1)) + min верхню межу?
Так, включає.
max - min + 1дає кількість можливих цілих результатів, аMath.floorу комбінації з+ minзабезпечує, щоmaxдосяжний. Наприклад, приmin=1, max=3ви отримаєте 1, 2 або 3, усі три значення можливі.
Чи можна використовувати Math.ceil замість Math.floor?
Технічно так, але розподіл стане нерівномірним. При
Math.ceil(Math.random() * range)значенняminвипадатиме тільки колиMath.random()поверне рівно 0, це зникаюче рідкісна подія.Math.floorдає кожному цілому в діапазоні рівний шанс, тому в стандартній формулі використовують саме його.
Math.random() — це справді випадкове число?
Ні, це псевдовипадкове число. Рушій JavaScript використовує детермінований алгоритм (зазвичай xorshift128+), який видає статистично рівномірну послідовність, але знаючи сід, можна передбачити всі наступні значення. Для криптографії це неприйнятно, використовуйте
crypto.getRandomValues().
Як згенерувати випадкове число з рухомою комою в діапазоні?
Приберіть
Math.floorі+ 1:Math.random() * (max - min) + min. Це дасть дробове відminвключно доmaxвиключаючи. Якщо потрібен і верхній кінець включно, замінітьmax - minнаmax - min + Number.EPSILON, але на практиці дробові діапазони рідко потребують включності верхньої межі.
Що швидше: Math.random() чи crypto.getRandomValues()?
Math.random()на порядки швидший. У бенчмарках на V8 різниця сягає стократного і більшого розриву, тому щоcrypto.getRandomValues()робить системний виклик до/dev/urandom(Linux) або аналога, аMath.random(), суто внутрішньопроцесний PRNG. Для генерації тисяч значень у циклі берітьMath.random(), для одного токена безпеки,crypto.getRandomValues().
Випадкове число за 30 секунд: що запам'ятати
Робоча конячка, Math.floor(Math.random() * (max - min + 1)) + min. Запам'ятайте три правила: завжди Math.floor (не ceil, не round), завжди + 1 у множнику (щоб max був досяжний), завжди + min у кінці (щоб зсунути діапазон). Ці три операції покривають усе, від кидка кубика до вибору випадкового переможця.
Для коду скидання паролів і токенів, crypto.getRandomValues(). Повільніше, зате криптостійко.
Якщо потрібно просто спробувати, скопіюйте HTML-приклад вище, збережіть як файл і відкрийте в браузері. Жодних залежностей, жодних збирачів. Працює всюди, де є <script>.



