Найти в Дзене
Информатика

Почему твой генератор случайных чисел — не случайный (и как с этим жить) 🎲

Представь: ты пишешь игру, где враги должны появляться непредсказуемо. Запускаешь код, а они выходят в одних и тех же местах. Снова запускаешь — опять то же самое. Как будто играешь против бота, который выучил твой сценарий наизусть. 😬 Добро пожаловать в мир псевдослучайности — концепции, которая звучит как оксюморон, но на самом деле управляет половиной цифрового мира вокруг тебя. Вот тебе неудобная правда: компьютер — это машина, которая делает ровно то, что ей сказали. Он физически не способен генерировать настоящую случайность. Всё, что он может — это считать по формулам так быстро и хитро, что результат выглядит случайным. Функция rand() в языке C — это не магия. Это математический алгоритм, который берёт одно число и превращает его в другое по определённой формуле. Каждый раз одинаково. Поэтому если ты запустишь программу дважды, получишь абсолютно идентичную последовательность чисел. int r1 = rand(); // Допустим, вернёт 41
int r2 = rand(); // Потом 18467
int r3 = rand(); //
Оглавление
повторяющаяся «случайность»
повторяющаяся «случайность»

Представь: ты пишешь игру, где враги должны появляться непредсказуемо. Запускаешь код, а они выходят в одних и тех же местах. Снова запускаешь — опять то же самое. Как будто играешь против бота, который выучил твой сценарий наизусть. 😬

Добро пожаловать в мир псевдослучайности — концепции, которая звучит как оксюморон, но на самом деле управляет половиной цифрового мира вокруг тебя.

Спойлер: компьютер не умеет быть случайным

Псевдослучайность
Псевдослучайность

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

Функция rand() в языке C — это не магия. Это математический алгоритм, который берёт одно число и превращает его в другое по определённой формуле. Каждый раз одинаково. Поэтому если ты запустишь программу дважды, получишь абсолютно идентичную последовательность чисел.

int r1 = rand(); // Допустим, вернёт 41
int r2 = rand(); // Потом 18467
int r3 = rand(); // Затем 6334

Запустишь программу снова — увидишь те же 41, 18467, 6334. И так до бесконечности. 🔁

Почему так? Потому что алгоритм стартует с одного и того же «зерна» (seed) — начального числа, с которого начинается вся цепочка вычислений.

Как обмануть систему (легально)

Инициализация seed временем
Инициализация seed временем

Решение простое: меняй зерно каждый раз, когда запускаешь программу.

Для этого используется функция srand(), которой нужно скормить что-то уникальное. И что может быть уникальнее, чем... время? ⏰

#include <time.h>

srand(time(NULL)); // Инициализируем генератор текущим временем
int random_value = rand(); // Теперь каждый запуск даёт новые числа

Функция time(NULL) возвращает количество секунд с 1 января 1970 года (да, программисты считают время именно так). Поскольку ты запускаешь программу в разные моменты, зерно всегда будет разным — и последовательность тоже.

⚠️ Важный нюанс: вызывай srand() только один раз в начале программы. Если спамить ею перед каждым rand(), можешь получить обратный эффект — одинаковые числа.

Где это работает (и где точно нет)

игры vs безопасность
игры vs безопасность

✅ Подходит для:

  • Игр — спавн врагов, генерация уровней, случайные события
  • Симуляций — моделирование поведения толпы, физических процессов
  • Тестирования — генерация тестовых данных для проверки кода
  • Алгоритмов рекомендаций — перемешивание контента, чтобы показывать разное

❌ Категорически не подходит для:

  • Шифрования — генерация паролей, ключей, токенов безопасности
  • Криптовалют — генерация приватных ключей кошельков
  • Онлайн-казино — там используются аппаратные генераторы настоящей случайности

Почему? Потому что псевдослучайную последовательность можно предсказать, если знать алгоритм и начальное зерно. Для шифрования это смертельно опасно. 🔐

Вот почему WhatsApp*, Telegram и другие мессенджеры используют совершенно другие методы генерации случайности — основанные на физических процессах вроде электромагнитных шумов. Это уже совсем другой уровень.

Магия диапазонов: получаем нужные числа

rand() генерирует числа от 0 до RAND_MAX (обычно это 32767). Но что, если тебе нужен кубик от 1 до 6? 🎲

Целые числа в диапазоне [0, N):

int dice = rand() % 6; // Даёт числа от 0 до 5

Операция остатка % — твой лучший друг. Делишь случайное число на 6, берёшь остаток — получаешь диапазон от 0 до 5.

Целые числа в диапазоне [A, B):

int damage = rand() % 50 + 10; // Урон от 10 до 59

Сначала получаешь диапазон [0, 50), потом сдвигаешь на +10.

Вещественные числа от 0 до 1:

double probability = (double)rand() / RAND_MAX;

Почему приведение типов? Потому что без (double) деление будет целочисленным, и ты получишь только 0 или 1. С приведением — красивое дробное число вроде 0.73521.

Теперь можешь масштабировать:

// Случайная скорость от -10.5 до 10.5
double speed = ((double)rand() / RAND_MAX) * 21.0 - 10.5;

Реальный пример: квадратное уравнение

квадратное уравнение
квадратное уравнение

Да, математика. Но не зевай — сейчас покажу, как это связано с реальным кодом. 💡

Задача: решить ax² + bx + c = 0 и найти корни.

Алгоритм классический:

  1. Вычислить дискриминант: D = b² − 4ac
  2. Если D < 0 → корней нет
  3. Иначе: x₁,₂ = (−b ± √D) / 2a
#include <stdio.h>
#include <math.h>

int main(void) {
double a, b, c, D, x1, x2;

printf("Коэффициенты a, b, c: ");
scanf("%lf %lf %lf", &a, &b, &c);

D = b * b - 4 * a * c;

if (D < 0) {
printf("Дискриминант %.2f < 0. Корней нет.\n", D);
return 0;
}

D = sqrt(D); // Вычисляем корень один раз
x1 = (-b + D) / (2.0 * a);
x2 = (-b - D) / (2.0 * a);

printf("x1 = %.2f\n", x1);
printf("x2 = %.2f\n", x2);

return 0;
}

Что здесь крутого:

  • Функция sqrt() из библиотеки <math.h> — извлечение квадратного корня
  • Проверка условий до вычислений — если корней нет, не тратим ресурсы
  • Переиспользование переменной D для хранения √D — экономия памяти

Математика — это не про школу, а про алгоритмы

Тригонометрия, логарифмы, степени — всё это есть в <math.h>. И это не абстракция. Вот где это реально используется:

  • sin(), cos() — графика в играх, анимация движения, звуковые волны
  • pow() — экспоненциальный рост (вирусность контента, рост популярности мемов)
  • log() — алгоритмы сжатия данных, анализ сложности алгоритмов
  • sqrt() — расчёт расстояний в 2D/3D пространстве, физика столкновений

Каждая функция — это инструмент. Чем больше инструментов знаешь, тем круче проекты можешь делать. 🛠️

Три вещи, которые нужно запомнить

1. Псевдослучайность — это норма. Настоящая случайность компьютерам не по зубам (если это не специализированное железо).

2. Всегда инициализируй генератор. srand(time(NULL)) в начале программы — обязательная практика.

3. Не используй rand() для безопасности. Для паролей, токенов и шифрования есть специальные криптографические генераторы.

🔥 Хочешь копнуть глубже? Полный учебный материал с детальными примерами, схемами и крутыми иллюстрациями ждёт тебя на нашем сайте!