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

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

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

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

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

Вот почему я подумал, что общая (неписаная) политика заключается в том, что никакая библиотека никогда не должна вызывать 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);

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


person Albert    schedule 10.10.2014    source источник
comment
если вы можете использовать библиотеки C ++ 11, вам следует взглянуть на en.cppreference. com / w / cpp / numeric / random   -  person Creris    schedule 10.10.2014
comment
В любом случае использование rand для научных вычислений не обязательно является хорошей идеей: базовая реализация часто представляет собой простую LCG, которая обычно просто не обеспечивает значений достаточного качества для научных приложений.   -  person slyfox    schedule 10.10.2014
comment
ИМО, это недостаток дизайна на твоей стороне. Когда вам нужны предсказуемые случайные значения (contradictio in adiecto само по себе), вы никогда не должны использовать глобальный генератор случайных чисел, который также может использовать любая другая часть вашей программы, включая библиотеки, которые вы используете. Даже если эти другие части не вызывают srand(), просто вызывая rand(), они также повлияют на глобальный генератор случайных чисел.   -  person Erich Kitzmueller    schedule 10.10.2014
comment
общая (неписаная) политика заключается в том, что никакая библиотека никогда не должна вызывать srand () - это разумный подход, но тогда некоторые авторы библиотек получат множество жалоб и запросов от людей, интересующихся, почему случайное поведение повторяется между запусками .... Возможно, лучше всего было бы иметь srand_if_not_yet_initialised() функцию в API, но для этого уже поздно. Предпочитайте версии C ++ 11.   -  person Tony Delroy    schedule 10.10.2014
comment
Вы мало что можете сделать для управления вызовами глобальных функций, по сути, весь код, использующий их, будет мешать друг другу. Чтобы изолировать себя (или другой фрагмент кода), используйте вместо этого функции C ++ 11.   -  person Niall    schedule 10.10.2014
comment
Извините, использование srand / rand из соображений безопасности, как утверждает парень из libxml2, является насмешкой.   -  person user515430    schedule 10.10.2014
comment
Также обратите внимание, что даже если вы можете запретить кому-либо еще вызывать srand (), все равно будет плохой идеей полагаться на последовательность, сгенерированную rand () для тестирования / проверки / и т. Д., Поскольку она может измениться, если / когда вы переключитесь на другую платформу. или даже более новая версия библиотек / ОС.   -  person Paul R    schedule 10.10.2014
comment
@ammoQ Как правило, вам нужны не предсказуемые случайные числа, а то, что когда вы получаете результат, вы хотите, чтобы они были повторяемыми. Хотя, конечно, все должно работать в общем случае, это позволяет вам показать, что он действительно работает для определенных входов, если это необходимо (рабочая конфигурация входных данных), но также означает, что вы можете легче отслеживать, как изменения влияют на программу, потому что вы используете те же входные данные.   -  person Baldrickk    schedule 10.10.2014
comment
Библиотека, изменяющая глобальное состояние (включая вызов srand()), не работает. Их ответ - пустая болтовня, пытаясь оправдать неоправданное. В остальном: если вам нужны воспроизводимые случайные числа для научных расчетов, rand() не оплачивает счет. Это нормально для игр или просто для игр, но не более того. В противном случае до C ++ 11 вы реализуете свои собственные, а в C ++ 11 вы используете <random>. (Но это не позволяет разработчикам библиотеки сорваться с крючка.)   -  person James Kanze    schedule 10.10.2014
comment
@TonyD Если разработчику библиотеки нужны случайные числа, он может потребовать вызова srand() перед инициализацией библиотеки. Или просто используйте свой собственный частный генератор случайных чисел (в этом случае они должны предоставлять средства для указания начального числа, чтобы вы могли протестировать код, использующий библиотеку).   -  person James Kanze    schedule 10.10.2014
comment
@JamesKanze Библиотека, изменяющая глобальное состояние (включая вызов srand ()), не работает. - Вызов rand изменяет глобальное состояние так же, как и srand. Неисправно то, что эти функции имеют глобальное состояние.   -  person Jim Balter    schedule 10.10.2014
comment
Зачем, черт возьми, libxml вообще нужна случайность?   -  person user541686    schedule 10.10.2014
comment
@ user515430 Безопасность? Он использует это, чтобы его результаты были воспроизводимы. Это очень хорошая идея при отладке алгоритмов со стохастическим компонентом.   -  person DuncanACoulter    schedule 10.10.2014
comment
Я согласен с тем, что приложение должно установить начальное число и оставить все как есть. Это скорее моральная поддержка, чем ответ, следовательно, это комментарий.   -  person DuncanACoulter    schedule 10.10.2014
comment
@JimBalter Ага. Библиотека, которой требуются случайные числа, должна либо иметь свой собственный ГСЧ (один из тех, что в <random> подойдет, если библиотека не нуждается в поддержке до C ++ 11), либо попросить пользователя предоставить его (например, std::random_shuffle).   -  person James Kanze    schedule 10.10.2014
comment
@ Mehrdad Я могу придумать несколько причин, но ни одна из них не подходит rand(). (Для большинства потребуется какой-то криптографически безопасный ГСЧ. Это означает, что ни rand(), ни что-либо в <random> не подходят.)   -  person James Kanze    schedule 10.10.2014
comment
Хотя легко собрать rand и рекомендовать новые альтернативы C ++ 11, здесь, похоже, упускается из виду то, что в многих однопоточных приложениях после вызова srand() с тем же начальным значением , и с одинаковыми (если есть) внешними входами выполнение как в коде библиотеки app , так и будет полностью детерминированным. Наличие вызова библиотеки rand() изменяет глобальное состояние, но не менее предсказуемо, чем если бы это сделал код приложения более высокого уровня; если призывы перемежаются таким же образом, никакого вреда не будет.   -  person Tony Delroy    schedule 10.10.2014
comment
@JamesKanze: Ну вот и мой вопрос: почему для синтаксического анализа XML может потребоваться криптографически безопасный ГСЧ (не говоря уже о том, что rand не один)?   -  person user541686    schedule 10.10.2014
comment
@ user515430: rand(), вероятно, используется для защиты от атак с алгоритмической сложностью, а не для какой-либо серьезной криптографии.   -  person ninjalj    schedule 10.10.2014
comment
В дополнение к другим ответам и комментариям, ИМО, вам следует отделить источник псевдослучайных чисел от остальной части алгоритма. Создайте класс RandomValues, который вы можете переключать между детерминированными (возможно, даже жестко запрограммированными значениями) и любым генератором случайных чисел, который вы хотите, и получить доступ к этому классу из вашего алгоритма. Это позволит вам безболезненно переключаться между детерминированными и случайными входами.   -  person Michael    schedule 10.10.2014


Ответы (4)


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

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

person MSalters    schedule 10.10.2014
comment
Да, конечно, я это знаю. Но вопрос не в этом. - person Albert; 10.10.2014
comment
Если вы примете во внимание последствия, вы поймете, почему библиотека не может предположить, что вы вызвали srand(), который инициализирует глобальный PRNG. У вас может не быть никаких причин для этого, потому что вы используете свой собственный ГПСЧ. Таким образом, они должны это назвать. - person MSalters; 10.10.2014
comment
@Albert: ответ на ваш буквальный вопрос - это бесполезно, по крайней мере, с точки зрения тестируемости / повторяемости. Единственный способ обойти это - инкапсулировать состояние вашего собственного ГСЧ; новая библиотека C ++ сделает это за вас. - person Oliver Charlesworth; 10.10.2014
comment
@MSalters: Это мой вопрос. Должна ли библиотека так не предполагать? Если библиотека этого не предполагает, это означает, что все инструменты, которые вызывают srand() после инициализации libxml2, сбрасывают генератор случайных чисел, используемый библиотекой. Таким образом, в обоих случаях для библиотеки не имеет смысла вызывать srand(). Или в моем мышлении есть изъян? - person Albert; 10.10.2014
comment
@Albert: Что не имеет никакого значения для всех тех приложений, которые используют rand() только для получения случайных чисел. Им нужно только знать, что srand() вызывается хотя бы один раз. - person MSalters; 10.10.2014
comment
Re: их ответ, поскольку srand не является ни поточно-безопасным, ни реентерабельным, поскольку он вызывается несколько раз из разных потоков, совершенно небезопасен (rand имеет аналогичные проблемы). Здесь существует реальная фундаментальная проблема, которая решается возможностью использования нескольких генераторов в C ++ 11. - person Benjamin Bannier; 10.10.2014
comment
@Albert Ваш вопрос очень похож на "Разве не должно быть мира во всем мире?" ... независимо от ответа, нет. То, что libxml2 вызывает srand, это факт. Просто двигайтесь дальше и делайте то, что предлагает этот ответ. - person Jim Balter; 10.10.2014
comment
На мой несколько неявный вопрос, является ли это ошибкой / проблемой в libxml2, до сих пор нет ответа. Или ответ, что для libxml2 это не так уж важно? (И в своем приложении я все равно должен использовать что-то другое - но это все равно было бы моим последствием.) - person Albert; 10.10.2014
comment
@Albert Это бессмысленное философское упражнение. Сопровождающий не собирается менять его только из-за обсуждения здесь. Это, конечно, не ошибка, но похоже, что это проблема для вас ... так что снова сделайте что-нибудь еще. - person Jim Balter; 10.10.2014
comment
@MSalters не имеет никакого значения ... нужно только знать, что srand() вызывается по крайней мере один раз. - это может иметь значение: гарантии периода были потеряны / сброшены, поэтому более ранние последовательности могут быть повторены намного раньше, чем в противном случае (не только - но наиболее существенно - когда несколько вызовов srand() используют say time(NULL) as семя). - person Tony Delroy; 10.10.2014
comment
@TonyD: Срок гарантии? Я не думаю, что это предусмотрено стандартом. Кроме того, не имеет значения, что libXML получает те же случайные значения, что и вы, потому что они тоже вызвали srand() с тем же семенем. - person MSalters; 10.10.2014
comment
@MSalters: многие реализации документируют период 2 ^ 32, и для кода разумно полагаться на поведение, определяемое реализацией, если это соответствует его целям переносимости. Кроме того, не имеет значения, что libXML получает те же случайные значения, что и вы, потому что они тоже вызывали srand () с тем же семенем. - это непонимание того, как работает rand() - он не будет давать libXML ту же последовательность, что и приложение, потому что они разделяют состояние, проблема в том, что если, скажем, приложение является серьезным потребителем случайных чисел, тогда вызов libXML srand() может перезапустить [под] последовательность приложение уже создано. - person Tony Delroy; 10.10.2014
comment
Так? Пусть A будет частью повторяющегося шаблона, сгенерированного между двумя вызовами srand, а B - остатком, тогда результирующая последовательность будет AABABABA и так далее, что более случайное, чем ABABABA. - person MSalters; 10.10.2014
comment
@MSalters Если вы серьезно верите, что игнорирование сокращенного периода и такие рассуждения имеют смысл, то, думаю, лучше всего называть это прекращением ... ;-P. - person Tony Delroy; 10.10.2014
comment
@TonyD: Последовательность AABABAB ... даже не периодична! В любом случае, учитывая подпоследовательность, состоящую только из A, только последняя позволяет вам со 100% уверенностью предсказать, что вывод будет продолжаться с B. Нулевая энтропия. - person MSalters; 10.10.2014
comment
это неправильное понимание того, как работает rand () - Нет, похоже, вы совершенно не поняли то, что он написал. он не собирается давать libXML ту же последовательность, что и приложение, потому что они разделяют состояние - он ничего не сказал о том, потому что они разделяют состояние, он сказал, если srand вызывается с тем же семенем. - person Jim Balter; 10.10.2014
comment
@JimBalter: Я уверен, что ничего не понял неправильно. Как написал MSalter в своем последнем комментарии, последовательность AABABAB ... даже не периодична! - это та самая проблема, которую я подчеркивал. Будь то приложение или библиотека, которые первыми вызвали srand() и использовали A, можно было бы разумно ожидать, что A не повторится так скоро, но второй вызов srand() аннулирует это ожидание, и A, увиденное дважды (за вычетом любых элементов, потребляемых другой библиотекой / приложением сам по себе вызывая rand()). Я упомянул, потому что они разделяют состояние, потому что подпоследовательности из A могут (повторно) просматриваться приложением и библиотекой. - person Tony Delroy; 20.10.2014
comment
Я уверен, что такая уверенность утешает. - person Jim Balter; 20.10.2014

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

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

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

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

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

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

person Damon    schedule 10.10.2014

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

std::default_random_engine e1

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

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

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

person wasylszujski    schedule 10.10.2014

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

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

Другие предложили использовать генерацию случайных чисел C ++ 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, который принимает параметр случайного генератора)

person Mats Petersson    schedule 10.10.2014
comment
Да! Так хорошо. Простое перемещение вызова srand() из библиотеки libxml на самом деле не сделает ваши результаты очень воспроизводимыми. Это может сработать, но очевидно, что некоторые функции libxml в какой-то момент вызывают rand() (вычисление UUID?), И это изменит псевдослучайную последовательность, которую ваша программа получит от rand() после этого ... - person Colin D Bennett; 10.10.2014