Почему использование rand() считается плохим?
Я слышал, как некоторые парни говорили, что использование rand()
плохо ДАЖЕ ПОСЛЕ ИСПОЛЬЗОВАНИЯ srand()
чтобы получить семя. Почему это так? Я хочу знать, как все происходит... И извините за еще один вопрос... но какая альтернатива этому тогда?
7 ответов
Эта история состоит из двух частей.
Первый, rand
генератор псевдослучайных чисел Это означает, что это зависит от семени. Для данного семени он всегда будет давать одну и ту же последовательность (при условии, что реализация будет одинаковой). Это делает его непригодным для определенных приложений, где безопасность имеет большое значение. Но это не относится к rand
, Это проблема любого псевдослучайного генератора. И, безусловно, существует множество классов задач, в которых приемлем псевдослучайный генератор. Истинный генератор случайных чисел имеет свои проблемы (эффективность, реализация, энтропия), поэтому для задач, не связанных с безопасностью, чаще всего используется генератор псевдослучайных данных.
Итак, вы проанализировали свою проблему и пришли к выводу, что псевдослучайный генератор является решением. И вот мы приходим к реальным проблемам случайной библиотеки C (которая включает в себя rand
а также srand
) которые являются специфическими для этого и делают его устаревшим (иначе: причины, которые вы никогда не должны использовать rand
и случайная библиотека C).
Одна проблема заключается в том, что он имеет глобальное состояние (устанавливается
srand
). Это делает невозможным использование нескольких случайных движков одновременно. Это также значительно усложняет многопоточные задачи.Наиболее заметная проблема заключается в том, что ему не хватает механизма распространения:
rand
дает вам номер в интервале[0 RAND_MAX]
, Он одинаков в этом интервале, что означает, что каждое число в этом интервале имеет одинаковую вероятность появления. Но чаще всего вам нужно случайное число в определенном интервале. Скажем[0, 1017]
, Обычно (и наивно) используемая формулаrand() % 1018
, Но проблема в том, что еслиRAND_MAX
является точным кратным1018
вы не получите равномерное распределение.Еще одной проблемой является качество реализации
rand
, Здесь есть другие ответы, детализирующие это лучше, чем я мог, поэтому, пожалуйста, прочитайте их.
В современном C++ вы обязательно должны использовать библиотеку C++ из <random>
который поставляется с несколькими случайными четко определенными механизмами (для целочисленных типов и типов с плавающей запятой) и различными распределениями.
Ни один из ответов здесь не объясняет истинную причину rand()
плохо
rand()
это генератор псевдослучайных чисел (PRNG), но это не значит, что он должен быть плохим. На самом деле, есть очень хорошие PRNG, которые статистически трудно или невозможно отличить от истинных случайных чисел.
rand()
полностью определяется реализацией, но исторически он реализован как линейный конгруэнтный генератор (LCG), который обычно является быстрым, но общеизвестно плохим классом PRNG. Младшие биты этих генераторов имеют гораздо меньшую статистическую случайность, чем старшие биты, и сгенерированные числа могут создавать видимые решетчатые и / или плоские структуры (лучший пример этого - знаменитый RANDU PRNG). Некоторые реализации пытаются уменьшить проблему младших битов, сдвигая биты вправо на заранее определенную величину, однако такое решение также уменьшает диапазон вывода.
Тем не менее, есть заметные примеры отличных LCG, таких как мультипликативные линейные конгруэнтные генераторы L'Ecuyer, 64 и 128 бит, представленные в таблицах линейных конгруэнтных генераторов разных размеров и хорошей структуры решетки, Pierre L'Ecuyer, 1999.
Общее правило: не доверяй rand()
, используйте свой собственный генератор псевдослучайных чисел, который соответствует вашим потребностям и требованиям использования.
Что плохого в rand
/srand
в том, что rand
-
- использует неопределенный алгоритм RNG, но
- позволяет инициализировать ГСЧ
srand
за повторяемость "случайности".
Эти два момента, взятые вместе, затрудняют способность реализаций улучшать реализацию ГСЧ (например, использовать криптографический или иным образом "лучший" ГСЧ). Например, JavaScript Math.random
и FreeBSD arc4random
У меня нет этой проблемы, поскольку они не позволяют приложениям заполнять их для повторяемой "случайности" - именно по этой причине движок JavaScript V8 смог изменить его Math.random
реализация к варианту xorshift128+
сохраняя обратную совместимость. (С другой стороны, позволяя приложениям предоставлять дополнительные данные для дополнения случайности, как в BCryptGenRandom
менее проблематично; однако даже в этом случае это обычно наблюдается только в криптографических RNG.)
Также:
- Тот факт, что алгоритм ГСЧ и процедура посева не определены, означает, что даже воспроизводимая "случайность" не гарантируется между
rand
/srand
реализации, между версиями одной и той же стандартной библиотеки, между операционными системами и т. д. - Если
srand
не вызывается раньшеrand
является,rand
ведет себя так же, как будтоsrand(1)
были впервые названы. На практике это означает, чтоrand
может быть реализован только как ГСЧ, а не как недетерминированный ГСЧ, и чтоrand
алгоритм PRNG не может отличаться в данной реализации, независимо от того, вызывает ли приложениеsrand
или нет.
Во-первых, srand()
не получает семя, оно устанавливает семя. Заполнение является частью использования любого генератора псевдослучайных чисел (PRNG). Последовательность чисел, которую PRNG производит из этого начального числа, является строго детерминированной, потому что (большинство?) Компьютеров не имеют средств для генерации истинных случайных чисел. Изменение вашего PRNG не помешает повторению последовательности из начального числа, и, действительно, это хорошо, потому что возможность генерировать одну и ту же последовательность псевдослучайных чисел часто бывает полезна.
Так что если все PRNG поделятся этой функцией с rand()
почему rand()
считается плохим? Ну, это сводится к псевдо-части псевдослучайного. Мы знаем, что PRNG не может быть действительно случайным, но мы хотим, чтобы он вел себя как можно ближе к генератору истинных случайных чисел, и существуют различные тесты, которые можно применять, чтобы проверить, насколько последовательность PRNG похожа на истинную случайную последовательность., Хотя его реализация не определена стандартом, rand()
в каждом обычно используемом компиляторе используется очень старый метод генерации, подходящий для очень слабого аппаратного обеспечения, и результаты, которые он дает достаточно плохо в этих тестах. С этого времени было создано много лучших генераторов случайных чисел, и лучше выбрать тот, который соответствует вашим потребностям, а не полагаться на некачественный генератор, который может быть предоставлен rand()
,
То, что подходит для ваших целей, зависит от того, что вы делаете, например, вам может потребоваться криптографическое качество или многомерная генерация, но для многих случаев, когда вы просто хотите, чтобы вещи были достаточно равномерно случайными, быстрая генерация и деньги не были потрачены. линия, основанная на качестве результатов, которые вы, вероятно, хотите генератор xoroshiro128+. В качестве альтернативы вы можете использовать один из методов в C++ <random>
заголовок, но предлагаемые генераторы не являются современными, и теперь гораздо лучше, но они все еще достаточно хороши для большинства целей и довольно удобны.
Если деньги находятся на линии (например, для перетасовки карт в онлайн-казино и т. Д.) Или вам необходимо криптографическое качество, вам необходимо тщательно исследовать соответствующие генераторы и убедиться, что они точно соответствуют вашим конкретным потребностям.
rand
обычно, но не всегда, по историческим причинам, очень плохой генератор псевдослучайных чисел(PRNG). Насколько это плохо, зависит от конкретной реализации.
C++ 11 имеет хорошие, намного лучшие, PRNG. Используйте его<random>
стандартный заголовок. Смотри особенноstd::uniform_int_distribution
здесь, который имеет хороший пример вышеstd::mersenne_twister_engine
,
PRNG - очень сложная тема. Я ничего о них не знаю, но доверяю экспертам.
Позвольте мне добавить еще одну причину, по которой rand() совершенно непригодна для использования: стандарт не определяет никаких характеристик генерируемых им случайных чисел, ни распределения, ни диапазона.
Без определения дистрибутива мы не можем даже обернуть его, чтобы получить тот дистрибутив, который нам нужен.
Более того, теоретически я могу реализовать функцию rand(), просто вернув 0 и объявив, что
RAND_MAX
моего rand() равно 0.
Или, что еще хуже, я могу позволить младшему значащему биту всегда быть 0, что не нарушает стандарт. Представьте, что кто-то пишет код, например
if (rand()%2) ...
.
На практике rand() определяется реализацией, и стандарты говорят:
Нет никаких гарантий относительно качества создаваемой случайной последовательности, и известно, что некоторые реализации создают последовательности с крайне неслучайными младшими битами. Приложения с особыми требованиями должны использовать генератор, который, как известно, достаточен для их нужд.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf стр. 36
Если вы используете rand(), вы получите тот же результат после генерации случайного числа. Таким образом, даже после использования srand() будет легко предсказать сгенерированное число, если кто-то сможет угадать семя, которое вы используете. Это связано с тем, что функция rand () использует специальный алгоритм для получения таких чисел.
Потратив некоторое время, вы можете выяснить, как предсказать числа, сгенерированные функцией, с учетом начального числа. Все, что вам нужно сейчас, это угадать семя. Некоторые люди называют семя текущим временем. Так что, если вы сможете угадать время, в которое вы запустите приложение, я смогу предсказать число
ПЛОХО ИСПОЛЬЗОВАТЬ RAND()!!!!