Случайные числа

Я думаю понятие случайных величин вам знакомо: они случайны. Иногда в самодельном устройстве бывает нужна случайная величина, например для какой-нибудь игры или световых эффектов. Монетка, игральный кубик или лото-машина являются отличными источниками случайных чисел, потому что это - физический процесс. Микроконтроллер же не может генерировать настоящие случайные числа, потому что он - точное вычислительное устройство, у которого нет случайностей. Что делать? Использовать такое понятие, как псевдослучайные числа. Псевдослучайное число получается путём различных математических действий с начальным числом, то есть имея начальное число, мы можем сгенерировать на его основе целую кучу других чисел. Но тут есть две проблемы:

  • Через какое-то количество итераций (тысяч, миллионов, а может и больше) ряд сгенерированных псевдослучайных чисел начнёт повторяться. Для наших целей это не так страшно, можно об этом не думать.
  • Генератору псевдослучайных чисел нужно начальное случайное число. И мы можем его получить - об этом ниже.

Arduino и случайные числа


Ардуино имеет пару готовых функций для работы с псевдослучайными числами, давайте на них посмотрим:

  • random(max) – возвращает псевдослучайное число в диапазоне от 0 до (max – 1). Принимает unsigned long*
  • random(min, max) – возвращает псевдослучайное число в диапазоне от min до (max – 1). Принимает unsigned long*
  • randomSeed(value) – установить новое начальное число для отсчёта, value – любое число типа unsigned long*

(*) - от 0 до 4 294 967 295

Как генерировать случайные числа, чтобы последовательность каждый раз была новая? Есть варианты:

  • Прочитать сигнал с никуда не подключенного аналогового пина и установить его в качестве начального: randomSeed(analogRead(0)). Пин ловит наводки из воздуха и сигнал с него действительно случайный. Подробнее об этом ниже.
  • Если устройство взаимодействует с окружающим миром или с пользователем, то можно при наступлении различных событий (нажатие кнопки, срабатывание датчика, принятие данных, и т.д.) скармливать в randomSeed() текущее время с момента запуска программы: randomSeed(micros()).
  • В esp8266 часто используют вариант c randomSeed(micros()) сразу после того, как будет установлено подключение к WiFi при запуске программы: время подключение каждый раз сильно отличается.

Аппаратный рандом?


Помните, в уроке про цифровые пины я говорил, что если пин никуда не подключен - то он ловит "из воздуха" всякие шумы и наводки? Эти шумы имеют природу, близкую к случайной, и было бы глупо этим не воспользоваться! Давайте посмотрим, какие значения можно получить с никуда не подключенного аналогового пина, опрашивая его обычным analogRead();

void loop() {
  Serial.println(analogRead(A0));
  delay(100);
}

Синусоида? Вполне логично, в стенах куча проводов под напряжением, вот они и наводят помехи на пин. Вполне можно сделать на этой основе настоящий аппаратный рандом.

Первые два бита из результата analogRead() имеют самый большой шум. Давайте на него посмотрим, выведя несколько результатов в график. Получить первые два бита можно так: analogRead(A0) & 3

for (int i = 0; i < 300; i++) {
    Serial.println(analogRead(A0) & 3);
}

Выглядит весьма случайно, никакой закономерности не прослеживается. Но значения меняются всего от 0 до 3, поэтому можно попробовать их перемножать и складывать, примерно так:

uint32_t seed = 0;
for (int i = 0; i < 16; i++) {
  seed *= 4;
  seed += analogRead(A0) & 3;
  randomSeed(seed);
}

Результат можно использовать для получения случайного значения для генератора случайных чисел при запуске программы.

Напоследок давайте посмотрим, какой результат даёт такой аппаратный рандом. График я построил в excel, чтобы более чётко видеть рассеивание полученного случайного значения:
Имеем практически равномерное рассеяние и огромное количество случайных значений, полученных путём перемножения и сложения "шума".

Случайный bool


Иногда бывает нужен случайный флаг, то есть true/false. Делается это очень просто: в уроке про условия я рассказывал, что bool принимает true при любом отличном от нуля значении. Это можно использовать для получения случайного логического значения с заданной вероятностью! Просто присваиваем логической переменной результат функции random(), в которую передаём число, обратное вероятности получения false:

bool rndFlag = random(5);

Переменная rndFlag получит значение false с вероятностью 1/5, то есть 20%. Если нужен true с низкой вероятностью - используем инверсию:

bool rndFlag = !random(10);

Теперь переменная rndFlag получит значение true с вероятностью 10%.

Библиотеки


Стандартный рандом выполняется относительно долго (~90 мкс), иногда это может мешать. Можно использовать быстрый рандом, реализацию я взял из библиотеки FastLED и оформил в виде отдельной библиотеки, документация и примеры есть по ссылке.

Видео


 

Полезные страницы


5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

7 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх