Math.random(): примеры (JAVA)
Math.random(): doubleОбщее описание
В Java метод Math.random() предоставляет простое средство получения псевдослучайного значения типа double в диапазоне от 0.0 включительно до 1.0 исключительно. Полная сигнатура: public static double Math.random(). Аргументы отсутствуют.
Возвращаемое значение представляет собой двоичное представление вещественного числа с плавающей точкой двойной точности в диапазоне [0.0, 1.0). Значение 0.0 может появляться, значение 1.0 не возвращается.
Особенности поведения:
- Определенность интерфейса: API стабилен, сигнатура не изменялась.
- Псевдослучайность: значения генерируются детерминированным генератором случайных чисел, который не предназначен для криптографических целей.
- Потокобезопасность и производительность: детали реализации и характеристики при многопоточном доступе зависят от версии JVM; для высокопроизводительных многопоточных сценариев чаще предпочитаются другие генераторы.
Вызов Math.random() не принимает параметров. Для получения чисел в других диапазонах, целых значений или логических случайных величин требуется масштабирование или дополнительные операции.
Примеры использования
Ниже приведены короткие примеры типичных вариантов применения. В каждом примере показан исходный код и возможный результат.
1) Базовое случайное вещественное число
double v = Math.random();
System.out.println(v);
0.37444887175646646
2) Случайное число в диапазоне 0-10 (с плавающей точкой)
double v = Math.random() * 10;
System.out.println(v);
6.283917309246662
3) Случайное целое от 0 до 9 (включительно 0, исключая 10)
int i = (int) (Math.random() * 10);
System.out.println(i);
3
4) Случайное целое в диапазоне [min, max] включительно
int min = 5, max = 12;
int r = (int) (Math.random() * (max - min + 1)) + min;
System.out.println(r);
8
5) Случайный булев тип (пример распределения 50/50)
boolean b = Math.random() < 0.5;
System.out.println(b);
true
6) Случайный индекс для массива
String[] a = {"a","b","c","d"};
int idx = (int) (Math.random() * a.length);
System.out.println(a[idx]);
c
Аналоги в Java и краткие отличия
Сравнение основных генераторов, доступных в Java:
- java.util.Random - класс с возможностью задания seed для воспроизводимости. Удобен при необходимости контролируемых последовательностей.
- java.util.concurrent.ThreadLocalRandom - оптимизирован для многопоточных приложений, избегает глобальной блокировки, предпочтителен в конкурентных сценариях.
- java.util.SplittableRandom - эффективен для параллельных потоков и стримов, обеспечивает хорошую производительность при разделении генераторов.
- java.security.SecureRandom - предназначен для криптографических нужд, предоставляет непредсказуемость и водит гораздо медленнее по сравнению с Math.random().
- Новые API java.util.random (Java 17+) - набор интерфейсов и реализаций для гибкой замены генераторов и улучшенной производительности; используются для задач с особыми требованиями к качеству RNG.
Когда и что предпочтительнее:
- Если требуется простота и нет требований к многопоточности или криптографии - Math.random() или Random.
- Для многопоточных циклов и параллельных стримов - ThreadLocalRandom или SplittableRandom.
- Для криптографических задач - SecureRandom.
Аналоги в других языках и отличия
Краткие примеры в популярных языках с отмеченными отличиями.
JavaScript - Math.random()
// возвращает число в [0,1)
const v = Math.random();
console.log(v);
0.9279162518800909
Отличие: синтаксис похож, типы динамические, не подходит для криптографии (есть crypto.getRandomValues).
Python - random.random() и secrets
import random
v = random.random()
print(v)
0.2195109149034761
Отличие: random обеспечивает псевдослучайность, secrets.choice или secrets.token_bytes рекомендуется для безопасности.
PHP - mt_rand / random_int
$v = mt_rand() / mt_getrandmax();
echo $v . "\n";
0.6123456789
Отличие: mt_rand быстрее и качественнее старого rand, random_int предоставляет криптографически безопасные целые.
SQL - RAND()
SELECT RAND();
0.482937284
Отличие: поведение и качество зависят от СУБД; часто используется для выборки случайных строк.
C# - System.Random и RandomNumberGenerator
var r = new Random();
double v = r.NextDouble();
Console.WriteLine(v);
0.3340912345
Отличие: Random не криптографичен, для безопасности используется System.Security.Cryptography.RandomNumberGenerator.
Lua - math.random
math.randomseed(os.time())
print(math.random())
0.7456123
Отличие: реализация и диапазон зависят от сборки Lua; часто возвращает целые в диапазоне, для дробных нужно деление.
Go - math/rand и crypto/rand
import ("fmt"; "math/rand")
fmt.Println(rand.Float64())
0.1283749123
Отличие: math/rand похож на Random, crypto/rand используется для криптографической стойкости.
Kotlin - kotlin.random.Random
val v = kotlin.random.Random.Default.nextDouble()
println(v)
0.559201234
Отличие: в Kotlin есть единый API с несколькими реализациями, можно использовать Java-генераторы при необходимости.
Типичные ошибки и их проявления
Ниже перечислены распространенные ошибки при использовании Math.random() с примерами.
1) Ожидание значения 1.0
double v = Math.random();
if (v == 1.0) System.out.println("equal");
else System.out.println("not equal");
not equal
Пояснение: Math.random() никогда не возвращает 1.0, проверка на равенство с 1.0 бессмысленна.
2) Неправильное вычисление диапазона (off-by-one)
int max = 10;
int wrong = (int) (Math.random() * max) + 1; // дает 1..10
int correctZeroToNine = (int) (Math.random() * max); // дает 0..9
wrong: 7 correctZeroToNine: 3
Пояснение: при необходимости диапазона [min, max] включительно нужно учитывать +1 в множителе.
3) Использование Math.random() для криптографии
// Неприемлемо для секретов
double v = Math.random();
// использование v для генерации пароля или ключа
(небезопасно - пример результата зависит от окружения)
Пояснение: для криптографии требуется SecureRandom или соответствующие системные вызовы.
4) Создание нового Random каждый вызов вместо переиспользования
// Ошибочный паттерн, не относится к Math.random но часто встречается
Random r = new Random();
int a = r.nextInt(100);
Random r2 = new Random();
int b = r2.nextInt(100);
a: 23 b: 23
Пояснение: повторная инициализация с одинаковым seed (например, по текущему времени) может привести к схожим последовательностям.
Изменения и история
API Math.random() сохраняет прежнюю сигнатуру долгие годы и изменений в поведении на уровне контракта не происходило. Основные отличия связаны с внутренними улучшениями реализации JVM и появлением новых генераторов:
- Появление java.util.concurrent.ThreadLocalRandom для улучшения многопоточной производительности.
- Введение java.util.SplittableRandom для эффективной работы в параллельных стримах.
- В Java 17 и выше добавлены новые интерфейсы и реализации в пакете java.util.random для гибкой конфигурации генераторов и более высокого качества случайности при необходимости.
Вывод: интерфейс Math.random() остается стабильным, но для специфичных задач рекомендуется рассматривать современные API.
Расширенные и редкие примеры
Несколько более продвинутых сценариев, где применяется Math.random() и объяснение ограничений.
1) Генерация нормального распределения методом Бокса - Мюллера
double u1 = Math.random();
double u2 = Math.random();
double r = Math.sqrt(-2.0 * Math.log(u1));
double theta = 2.0 * Math.PI * u2;
double z = r * Math.cos(theta); // стандартное нормальное распределение
System.out.println(z);
-0.4312098476123456
Пояснение: Math.random() подходит для метода Бокса - Мюллера, но для большого объема данных и многопотоков лучше рассмотреть SplittableRandom или специализированные библиотеки.
2) Перемешивание массива (Fisher-Yates)
int[] a = {1,2,3,4,5};
for (int i = a.length - 1; i > 0; i--) {
int j = (int) (Math.random() * (i + 1));
int tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
System.out.println(java.util.Arrays.toString(a));
[3, 5, 1, 4, 2]
Пояснение: корректный алгоритм перемешивания использует равномерное целое j в [0, i], достижимый через (int)(Math.random()*(i+1)).
3) Резервуарная выборка (Reservoir Sampling) для случайного элемента из потока неизвестного размера
int chosen = -1;
int count = 0;
java.util.Scanner sc = new java.util.Scanner("a b c d e");
while (sc.hasNext()) {
String item = sc.next();
count++;
if (Math.random() < 1.0 / count) {
chosen = count; // сохраняется индекс или значение
}
}
System.out.println(chosen);
3
Пояснение: алгоритм позволяет выбрать равновероятный элемент из потока без хранения всего набора. Math.random() обеспечивает необходимую случайность при отсутствии криптографических требований.
4) Генерация случайной шестнадцатеричной строки
int len = 16; // байт
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
int b = (int) (Math.random() * 256);
sb.append(String.format("%02x", b));
}
System.out.println(sb.toString());
7f3a9c01b4e2d6f8a0c1e2b3d4f5a6b7
Пояснение: результат не подходит в качестве секретного токена из-за предсказуемости; для такого назначения применяется SecureRandom.
5) Комбинирование с потоками: недостатки при высокой конкуренции
// Эскиз: многопоточный вызов Math.random()
Runnable r = () -> {
for (int i = 0; i < 1000; i++) Math.random();
};
Thread t1 = new Thread(r), t2 = new Thread(r);
t1.start(); t2.start();
(нет явного вывода, но возможна потеря производительности в старых реализациях JVM)
Пояснение: в многопоточных нагрузках предпочтительнее ThreadLocalRandom или новые API java.util.random.