Представь: ты пишешь игру, где враги должны появляться непредсказуемо. Запускаешь код, а они выходят в одних и тех же местах. Снова запускаешь — опять то же самое. Как будто играешь против бота, который выучил твой сценарий наизусть. 😬
Добро пожаловать в мир псевдослучайности — концепции, которая звучит как оксюморон, но на самом деле управляет половиной цифрового мира вокруг тебя.
Спойлер: компьютер не умеет быть случайным
Вот тебе неудобная правда: компьютер — это машина, которая делает ровно то, что ей сказали. Он физически не способен генерировать настоящую случайность. Всё, что он может — это считать по формулам так быстро и хитро, что результат выглядит случайным.
Функция rand() в языке C — это не магия. Это математический алгоритм, который берёт одно число и превращает его в другое по определённой формуле. Каждый раз одинаково. Поэтому если ты запустишь программу дважды, получишь абсолютно идентичную последовательность чисел.
int r1 = rand(); // Допустим, вернёт 41
int r2 = rand(); // Потом 18467
int r3 = rand(); // Затем 6334
Запустишь программу снова — увидишь те же 41, 18467, 6334. И так до бесконечности. 🔁
Почему так? Потому что алгоритм стартует с одного и того же «зерна» (seed) — начального числа, с которого начинается вся цепочка вычислений.
Как обмануть систему (легально)
Решение простое: меняй зерно каждый раз, когда запускаешь программу.
Для этого используется функция srand(), которой нужно скормить что-то уникальное. И что может быть уникальнее, чем... время? ⏰
#include <time.h>
srand(time(NULL)); // Инициализируем генератор текущим временем
int random_value = rand(); // Теперь каждый запуск даёт новые числа
Функция time(NULL) возвращает количество секунд с 1 января 1970 года (да, программисты считают время именно так). Поскольку ты запускаешь программу в разные моменты, зерно всегда будет разным — и последовательность тоже.
⚠️ Важный нюанс: вызывай srand() только один раз в начале программы. Если спамить ею перед каждым rand(), можешь получить обратный эффект — одинаковые числа.
Где это работает (и где точно нет)
✅ Подходит для:
- Игр — спавн врагов, генерация уровней, случайные события
- Симуляций — моделирование поведения толпы, физических процессов
- Тестирования — генерация тестовых данных для проверки кода
- Алгоритмов рекомендаций — перемешивание контента, чтобы показывать разное
❌ Категорически не подходит для:
- Шифрования — генерация паролей, ключей, токенов безопасности
- Криптовалют — генерация приватных ключей кошельков
- Онлайн-казино — там используются аппаратные генераторы настоящей случайности
Почему? Потому что псевдослучайную последовательность можно предсказать, если знать алгоритм и начальное зерно. Для шифрования это смертельно опасно. 🔐
Вот почему 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 и найти корни.
Алгоритм классический:
- Вычислить дискриминант: D = b² − 4ac
- Если D < 0 → корней нет
- Иначе: 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() для безопасности. Для паролей, токенов и шифрования есть специальные криптографические генераторы.
🔥 Хочешь копнуть глубже? Полный учебный материал с детальными примерами, схемами и крутыми иллюстрациями ждёт тебя на нашем сайте!