Полезность `rand()` - или кто должен называть `srand()`?

Справочная информация. Я использую rand(), std::rand(), std::random_shuffle() и другие функции в моем коде для научных вычислений. Чтобы иметь возможность воспроизводить мои результаты, я всегда четко указываю случайное семя и устанавливаю его через srand(). Это было хорошо до недавнего времени, когда я понял, что libxml2 также будет лгать srand() при первом использовании - это было после моего раннего вызова srand().

Я заполнил отчет об ошибке в libxml2 о его вызове srand(), но я получил ответ:

Сначала инициализируйте libxml2. Это совершенно законный вызов, который должен быть сделан из библиотеки. Вам следует не ожидайте, что никто не называет srand(), а man-страница нигде заявляет, что следует избегать использования srand() нескольких времен

На самом деле это мой вопрос. Если общая политика заключается в том, что каждый lib может/должен/должен/будет вызывать srand(), и я могу/мог бы также называть его здесь и там, я действительно не вижу, как это может быть полезно вообще. Или как rand() полезно тогда?

Вот почему я думал, что общая (неписаная) политика заключается в том, что lib никогда не должен называть srand(), и приложение должно называть его только один раз в начале. (Не принимая во внимание многопоточность. Думаю, в этом случае вы все равно должны использовать что-то другое.)

Я также попытался исследовать бит, который другие библиотеки на самом деле называют srand(), но я не нашел его. Есть ли?

Моим текущим обходным решением является этот уродливый код:

{
    // On the first call to xmlDictCreate,
    // libxml2 will initialize some internal randomize system,
    // which calls srand(time(NULL)).
    // So, do that first call here now, so that we can use our
    // own random seed.
    xmlDictPtr p = xmlDictCreate();
    xmlDictFree(p);
}

srand(my_own_seed);

Вероятно, единственным чистым решением было бы не использовать это вообще и использовать только мой собственный случайный генератор (возможно, через С++ 11 <random>). Но на самом деле это не вопрос. Вопрос в том, кто должен вызывать srand(), а если все это делают, то как rand() полезно тогда?

Ответ 1

Вместо этого используйте новый заголовок <random>. Он позволяет использовать несколько экземпляров двигателей, используя разные алгоритмы и, что более важно, для вас, независимые семена.

[править] Чтобы ответить на "полезную" часть, rand генерирует случайные цифры. Это то, что хорошо. Если вам нужен мелкозернистый контроль, включая воспроизводимость, вы должны иметь не только известный семя, но и известный алгоритм. srand в лучшем случае дает вам фиксированное семя, так что это не полное решение.

Ответ 2

Ну, очевидная вещь была высказана несколько раз другими, используйте новые генераторы С++ 11. Я повторяю это по другой причине. Вы используете вывод для научных вычислений, а rand обычно реализует довольно плохой генератор (в то время во многих реализациях основного потока используется MT19937, который помимо плохого восстановления состояния не так уж плох, но у вас нет гарантии для конкретного алгоритма, и по крайней мере один основной компилятор по-прежнему использует очень плохой LCG).

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

Важное примечание: std::random_shuffle (версия с двумя параметрами) может на самом деле вызвать rand, что является ошибкой, о которой следует знать, если вы используете ее, даже если вы в противном случае используете новый С++ 11 генераторы найдены в <random>.

О фактической проблеме, вызывающей srand дважды (или даже чаще), не проблема. Вы можете в принципе называть его так часто, как хотите, все, что он делает, это изменение семени и, следовательно, последующая псевдослучайная последовательность. Мне интересно, почему XML-библиотека захочет называть это вообще, но они правы в своем ответе, для них не является незаконным. Но это также не имеет значения.
Единственное, что нужно сделать, это то, что либо вы не хотите получать какую-либо конкретную псевдослучайную последовательность (то есть, любая последовательность будет делать, вам не интересно воспроизводить точную последовательность), либо вы будете последним srand, который отменяет любые предыдущие вызовы.

Таким образом, реализация вашего собственного генератора с хорошими статистическими свойствами и достаточно длительный период в 3-5 строках кода не так уж и трудна, с небольшой осторожностью. Главное преимущество (кроме скорости) состоит в том, что вы точно контролируете свое состояние и изменяете его.
Маловероятно, что вам понадобятся периоды намного дольше, чем 2 128 потому что явное запретное время фактически потребляет столько чисел. Компьютер 3GHz, потребляющий по одному числу каждый цикл, будет работать в течение 10 21 лет в период 2 128 поэтому для людей со средними сроками жизни не так много. Даже если предположить, что суперкомпьютер, с которым вы запускаете свое симуляцию, в триллионе раз быстрее, ваши великие великие дети не доживут конца периода.
В то время как периоды, подобные 2 19937 которые поставляют нынешние "современные" генераторы, действительно смешны, что пытается улучшить генератор на неправильном конце, если вы спросите меня (лучше убедиться, что они статистически устойчивы и быстро восстанавливаются из состояния наихудшего случая и т.д.). Но, конечно, мнения здесь могут отличаться.

Этот сайт содержит несколько быстрых генераторов с реализациями. Они являются генераторами xorshift в сочетании с шагом добавления или умножения и небольшим (от 2 до 64 машинных слов) отставанием, что приводит как к быстрым, так и к высококачественным генераторам (там также есть тестовый набор, а автор сайта написал пару документы по этому вопросу). Я использую модификацию одного из них (2-слово 128-битная версия портирована на 64-битные, с измененными изменениями соответственно).

Ответ 3

Эта проблема решается в генерации случайных чисел С++ 11, т.е. вы можете создать экземпляр класса:

std::default_random_engine e1

который позволяет полностью контролировать только случайные числа, созданные из объекта e1 (в отличие от того, что будет использоваться в libxml). Таким образом, общее правило будет заключаться в использовании новой конструкции, так как вы можете генерировать свои случайные числа независимо.

Очень хорошая документация

Чтобы решить ваши проблемы - я также думаю, что было бы плохой практикой вызывать srand() в библиотеке, подобной libxml. Тем не менее, более того, srand() и rand() не предназначены для использования в контексте, который вы пытаетесь использовать, - их достаточно, когда вам просто нужны случайные числа, как это делает libxml. Однако, когда вам нужна воспроизводимость и убедитесь, что вы независимы от других, новый заголовок <random> - это путь для вас. Итак, чтобы подвести итог, я не думаю, что это хорошая практика на стороне библиотеки, но трудно обвинить их в этом. Кроме того, я не мог себе представить, как они меняются, поскольку от этого, вероятно, зависит миллиард других программных продуктов.

Ответ 4

Настоящий ответ заключается в том, что если вы хотите быть уверенным, что ваша последовательность случайных чисел не изменяется каким-либо другим кодом, вам нужен случайный номер, который является приватным для вашей работы. Обратите внимание, что вызов srand является лишь малой частью этого. Например, если вы вызываете какую-либо функцию в другой библиотеке, которая вызывает rand, она также нарушит последовательность ваших случайных чисел.

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

Другие предложили использовать генерацию случайных чисел С++ 11, что является одним из решений.

В Linux и других совместимых библиотеках вы также можете использовать rand_r, который берет указатель на unsigned int на семя, которое используется для этой последовательности. Поэтому, если вы инициализируете переменную seed, а затем используйте ее со всеми вызовами rand_r, она будет создавать уникальную последовательность для ВАШЕГО кода. Это, конечно, все тот же старый генератор rand, просто отдельное семя. Основная причина, по которой я имею в виду, состоит в том, что вы можете довольно легко сделать что-то вроде этого:

int myrand()
{
   static unsigned int myseed = ... some initialization of your choice ...;
   return rand_r(&myseed);
}

и просто вызывать myrand вместо std::rand (и должен выполняться для работы в std::random_shuffle, который принимает произвольный параметр генератора)