Skip to content

Wszystko o WordPressie, tworzeniu stron — i nie tylko

🎲 Jak wygenerować losową liczbę całkowitą z zakresu w JavaScript

🎲 Jak wygenerować losową liczbę całkowitą z zakresu w JavaScript

Liczba losowa jest potrzebna wszędzie. ID zamówienia w sklepie internetowym, kod resetowania hasła, rzut kością w grze przeglądarkowej, wybór zwycięzcy konkursu. I prawie zawsze programista pisze tę samą linijkę: Math.random().

Ale między „uzyskać losowy ułamek od 0 do 1" a „uzyskać losową liczbę całkowitą od 5 do 20" jest przepaść złożona z trzech operacji, w której początkujący regularnie popełniają błędy: Math.floor i Math.ceil, plus jeden czy nie plus, zamieszanie, które daje albo wyjście poza zakres, albo nierównomierny rozkład.

Rozłożymy wzór na czynniki pierwsze, napiszemy działający generator w czystym HTML+CSS+JS i zajrzymy tam, gdzie Math.random() już nie wystarcza.

💡 Szybki przegląd:

  • Proszę rozłożyć wzór Math.floor(Math.random() * (max - min + 1)) + min, zrozumieć każdą operację.
  • Proszę dowiedzieć się, dlaczego tylko Math.floor, a nie Math.ceil czy Math.round, na przykładach zniekształceń zakresu.
  • Proszę skopiować gotowy generator HTML: dwa pola, przycisk, wyświetlanie wyniku, czysty JS, bez frameworków.
  • Proszę opanować Crypto.getRandomValues, gdy Math.random nie jest odpowiednie i potrzebna jest kryptograficznie bezpieczna alternatywa.

Jak działa Math.random()

Math.random(), statyczna metoda wbudowanego obiektu Math. Nie przyjmuje argumentów i zwraca pseudolosową liczbę zmiennoprzecinkową z zakresu [0, 1), zgodnie ze specyfikacją MDN. Nawias kwadratowy przy zerze oznacza „włącznie", okrągły przy jedynce „wyłączając". Oznacza to, że mogą Państwo otrzymać na przykład 0.0, wartość bliską 0.372 lub bliską 0.999, ale nigdy nie otrzymają Państwo dokładnie 1.0.

Rozkład jest w przybliżeniu równomierny. Implementacja jest wbudowana w silnik (V8, SpiderMonkey, JavaScriptCore), a konkretnego algorytmu (zazwyczaj xorshift128+) programista nie wybiera ani nie resetuje, ziarno (seed) jest ustawiane przez silnik podczas startu.

Samo Math.random() jest bezużyteczne w zadaniach praktycznych. Gry potrzebują liczb całkowitych, loterie, liczb całkowitych z zakresu, kryptografia, kryptograficznie bezpiecznej entropii, której Math.random() nie zapewnia. Dlatego zawsze nakłada się na nie wzór sprowadzający do zakresu.

Wzór: losowa liczba całkowita z zakresu [min, max]

Podstawowy wzór, który pokrywa zdecydowaną większość przypadków w web developmencie:

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

Rozłożymy to na przykładzie szkoleniowym z min = 5 i max = 10, opierając się na dokumentacji Math.random:

  • Załóżmy, że Math.random() zwrócił wartość R z zakresu [0, 1).
  • max - min + 1 = 10 - 5 + 1 = 6, liczba możliwych wyników całkowitych: 5, 6, 7, 8, 9, 10.
  • R * 6, przeskalowaliśmy losowy ułamek przez szerokość zakresu.
  • Math.floor(R * 6), odrzuciliśmy część ułamkową w dół. Teraz mamy indeks od 0 do 5.
  • индекс + min, przesunęliśmy o min. Gotowe: losowa liczba całkowita od 5 do 10 włącznie.

Ten sam wzór w zapisie jednowierszowym:

1const roll = Math.floor(Math.random() * (max - min + 1)) + min;

Dlaczego Math.floor, a nie Math.ceil czy Math.round

Wybór metody zaokrąglania ma krytyczny wpływ na rozkład. Pokażemy to na przykładzie szkoleniowym z zakresem od 1 do 3 (wszystkie wartości, poza granicami przedziałów, są obliczeniowe). Podejście opisano w kanonicznej odpowiedzi na StackOverflow.

Z Math.floor i wzorem (max - min + 1):

  • Jeśli Math.random() trafi w pierwszą jedną trzecią zakresu [0, 1): pomnożenie przez 3 da wartość od 0 do prawie 1, Math.floor odrzuci część ułamkową, wyjdzie indeks 0, plus min, wypadnie 1 (dokładnie jedna trzecia przypadków).
  • Jeśli Math.random() jest w środkowej jednej trzeciej: pomnożenie przez 3 da od 1 do prawie 2, Math.floor → indeks 1, plus min2 (kolejna jedna trzecia).
  • Jeśli Math.random() jest w ostatniej jednej trzeciej: pomnożenie przez 3 da od 2 do prawie 3, Math.floor → indeks 2, plus min3 (ostatnia jedna trzecia).

Równomiernie.

Co będzie z Math.ceil? Math.ceil(Math.random() * 3) da 1, 2, 3, ale szansa na 1 będzie znikomo mała (tylko gdy Math.random() jest równe dokładnie 0, co prawie nigdy się nie zdarza). Skrajne wartości są zniekształcone.

Math.round również jest nierównomierny: skrajne wartości otrzymują połowę szansy w stosunku do środkowych. Dlatego w generowaniu losowych liczb całkowitych używa się wyłącznie Math.floor.

Pełny przykład: generator w HTML, CSS i JavaScript

Działająca strona, którą można zapisać jako .html i otworzyć w przeglądarce. Dwa pola wejściowe, przycisk, wynik na zielonym tle.

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>

Kod jest celowo napisany w czystym JavaScripcie, bez frameworków i narzędzi do budowania, dzięki czemu można go skopiować i uruchomić natychmiast, bez npm install. Funkcja randomInt przyjmuje min i max, zwraca liczbę całkowitą włącznie na obu końcach. Obsługa przycisku odczytuje wartości z pól, waliduje je i wyświetla wynik.

Kiedy Math.random() nie wystarcza: Crypto.getRandomValues()

Math.random(), generator pseudolosowy. Wystarcza do gier, animacji, testów A/B i wyboru losowego elementu tablicy. Ale do zadań, gdzie przewidywalność oznacza podatność na ataki, nie nadaje się: tokeny resetowania hasła, klucze API, kryptograficzne nonce, generatory jednorazowych linków.

Proszę sobie wyobrazić: wysyłają Państwo użytkownikowi link do resetowania hasła z tokenem wygenerowanym przez Math.random(). Znając ziarno i algorytm (a są one standardowe dla silnika), atakujący może odtworzyć sekwencję i odgadnąć token. Właśnie dlatego każdy kod związany z bezpieczeństwem musi używać kryptograficznie bezpiecznego źródła losowości.

Dla takich scenariuszy istnieje Web Crypto API, crypto.getRandomValues(). Wykorzystuje ono kryptograficznie bezpieczne źródło entropii systemu operacyjnego i wypełnia przekazaną tablicę typowaną losowymi wartościami.

Generowanie kryptograficznie bezpiecznej losowej liczby całkowitej z zakresu:

1function 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}

Tutaj stosuje się próbkowanie z odrzucaniem (rejection sampling): funkcja żąda losowej 32-bitowej wartości i odrzuca te, które wykraczają poza granice równomiernie podzielnego zakresu, co eliminuje obciążenie modulo (modulo bias), gdy jedne wartości wypadają częściej niż inne.

W praktyce dla większości aplikacji webowych wystarcza Math.random(). Ale jeśli piszą Państwo generator jednorazowych linków lub kodów weryfikacyjnych, proszę użyć crypto.getRandomValues().

Wideo: generowanie losowej liczby w JavaScripcie

Krótka demonstracja z kanału dcode, omówienie Math.random() i wzoru na zakres z żywym przykładem w konsoli przeglądarki:

⁉️🤔 Często zadawane pytania

Czy wzór Math.floor(Math.random() * (max - min + 1)) + min obejmuje górną granicę?

Tak, obejmuje. max - min + 1 daje liczbę możliwych wyników całkowitych, a Math.floor w połączeniu z + min zapewnia, że max jest osiągalne. Na przykład przy min=1, max=3 otrzymają Państwo 1, 2 lub 3, wszystkie trzy wartości są możliwe.

Czy można użyć Math.ceil zamiast Math.floor?

Technicznie tak, ale rozkład stanie się nierównomierny. Przy Math.ceil(Math.random() * range) wartość min wypadnie tylko wtedy, gdy Math.random() zwróci dokładnie 0, a to jest zdarzenie skrajnie rzadkie. Math.floor daje każdej liczbie całkowitej w zakresie równe szanse, dlatego w standardowym wzorze używa się właśnie jego.

Math.random(), czy to naprawdę losowa liczba?

Nie, to liczba pseudolosowa. Silnik JavaScript używa deterministycznego algorytmu (zazwyczaj xorshift128+), który generuje statystycznie równomierną sekwencję, ale znając ziarno, można przewidzieć wszystkie kolejne wartości. Dla kryptografii jest to niedopuszczalne, proszę używać crypto.getRandomValues().

Jak wygenerować losową liczbę zmiennoprzecinkową z zakresu?

Proszę usunąć Math.floor i + 1: Math.random() * (max - min) + min. Da to ułamek od min włącznie do max wyłączając. Jeśli potrzebny jest również górny koniec włącznie, proszę zastąpić max - min przez max - min + Number.EPSILON, ale w praktyce zakresy ułamkowe rzadko wymagają włączania górnej granicy.

Co jest szybsze: Math.random() czy crypto.getRandomValues()?

Math.random() jest o rzędy wielkości szybsze. W benchmarkach na V8 różnica sięga stukrotności i więcej, ponieważ crypto.getRandomValues() wykonuje wywołanie systemowe do /dev/urandom (Linux) lub odpowiednika, a Math.random() to czysto wewnątrzprocesowy PRNG. Do generowania tysięcy wartości w pętli proszę użyć Math.random(), do pojedynczego tokena bezpieczeństwa, crypto.getRandomValues().

Losowa liczba w 30 sekund: co zapamiętać

Koń roboczy, Math.floor(Math.random() * (max - min + 1)) + min. Proszę zapamiętać trzy zasady: zawsze Math.floor (nie ceil, nie round), zawsze + 1 w mnożniku (aby max było osiągalne), zawsze + min na końcu (aby przesunąć zakres). Te trzy operacje pokrywają wszystko, od rzutu kością po wybór losowego zwycięzcy.

Dla kodu resetowania haseł i tokenów, crypto.getRandomValues(). Wolniejsze, ale kryptograficznie bezpieczne.

Jeśli chcą Państwo po prostu spróbować, proszę skopiować przykład HTML powyżej, zapisać jako plik i otworzyć w przeglądarce. Żadnych zależności, żadnych narzędzi do budowania. Działa wszędzie tam, gdzie jest <script>.