Рекомендуемый способ инициализации srand?

Мне нужен "хороший" способ инициализировать генератор псевдослучайных чисел в С++. Я нашел статью, в которой говорится:

Чтобы генерировать случайные числа, srand обычно инициализируется к некоторой отличительной ценности, например связанных с временем выполнения. Для Например, значение, возвращаемое время функции (объявлено в заголовке ctime) различается каждую секунду, что достаточно для большинства randoming потребности.

Unixtime недостаточно для моего приложения. Какой лучший способ инициализировать это? Бонусные очки, если они переносимы, но код будет в основном работать на хостах Linux.

Я подумывал сделать математику pid/unixtime для получения int или, возможно, чтения данных из /dev/urandom.

Спасибо!

ИЗМЕНИТЬ

Да, я фактически запускаю свое приложение несколько раз в секунду, и я столкнулся с конфликтами.

Ответы

Ответ 1

Лучший ответ - использовать материал случайного числа Boost. Если у вас есть доступ к С++ 11, используйте заголовок <random>.

Но если мы говорим о rand() и srand()
Лучше всего использовать time():

int main()
{
    srand(time(NULL));

    ...
}

Обязательно сделайте это в начале вашей программы, и не каждый раз, когда вы вызываете rand()!

При каждом запуске функция time() возвращает уникальное значение (если вы не запускаете приложение несколько раз в секунду). В 32-битных системах он будет повторяться только каждые 60 лет.

Я знаю, что вы не думаете, что время уникально, но мне трудно поверить. Но я, как известно, ошибаюсь.

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

ОК, поэтому, если вы действительно думаете, что вы запускаете несколько приложений в секунду.
Затем используйте таймер на таймере.

 int main()
 {
     struct timeval time; 
     gettimeofday(&time,NULL);

     // microsecond has 1 000 000
     // Assuming you did not need quite that accuracy
     // Also do not assume the system clock has that accuracy.
     srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

     // The trouble here is that the seed will repeat every
     // 24 days or so.

     // If you use 100 (rather than 1000) the seed repeats every 248 days.

     // Do not make the MISTAKE of using just the tv_usec
     // This will mean your seed repeats every second.
 }

Ответ 2

Это то, что я использовал для небольших программ командной строки, которые могут выполняться часто (несколько раз в секунду):

unsigned long seed = mix(clock(), time(NULL), getpid());

Где смесь:

// http://www.concentric.net/~Ttwang/tech/inthash.htm
unsigned long mix(unsigned long a, unsigned long b, unsigned long c)
{
    a=a-b;  a=a-c;  a=a^(c >> 13);
    b=b-c;  b=b-a;  b=b^(a << 8);
    c=c-a;  c=c-b;  c=c^(b >> 13);
    a=a-b;  a=a-c;  a=a^(c >> 12);
    b=b-c;  b=b-a;  b=b^(a << 16);
    c=c-a;  c=c-b;  c=c^(b >> 5);
    a=a-b;  a=a-c;  a=a^(c >> 3);
    b=b-c;  b=b-a;  b=b^(a << 10);
    c=c-a;  c=c-b;  c=c^(b >> 15);
    return c;
}

Ответ 3

Если вам нужен лучший генератор случайных чисел, не используйте libc rand. Вместо этого просто используйте что-то вроде /dev/random или /dev/urandom напрямую (прямо в нем int или что-то в этом роде).

Единственное реальное преимущество libc rand заключается в том, что с учетом семени он предсказуем, что помогает при отладке.

Ответ 4

В окнах:

srand(GetTickCount());

обеспечивает лучшее семя, чем time() с момента его в миллисекундах.

Ответ 5

С++ 11 random_device

Если вам нужно разумное качество, вы не должны использовать rand() в первую очередь; вы должны использовать библиотеку <random>. Он обеспечивает множество отличных функций, таких как различные механизмы для различных компромиссов качества, размера и производительности, повторного ввода и предопределенных дистрибутивов, чтобы вы не попали им неправильно. Он может даже обеспечить легкий доступ к недетерминированным случайным данным (например,/dev/random), в зависимости от вашей реализации.

#include <random>
#include <iostream>

int main() {
    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    std::mt19937 eng(seed);

    std::uniform_int_distribution<> dist{1,100};

    for (int i=0; i<50; ++i)
        std::cout << dist(eng) << '\n';
}

eng - источник случайности, здесь встроенная реализация mersenne twister. Мы засеваем его с помощью random_device, которое в любой достойной реализации будет недеструктивным RNG, а seed_seq объединяет более 32 бит случайных данных. Например, в libС++ random_device обращается /dev/urandom по умолчанию (хотя вы можете дать ему другой файл для доступа).

Далее мы создаем такое распределение, что при заданном источнике случайности повторные вызовы распределения будут давать равномерное распределение int от 1 до 100. Затем мы продолжим использовать распределение повторно и распечатать результаты.

Ответ 6

Лучший способ - использовать другой генератор псевдослучайных чисел. Мерсенн твистер (и Уичманн-Хилл) - моя рекомендация.

http://en.wikipedia.org/wiki/Mersenne_twister

Ответ 7

Я предлагаю вам увидеть файл unix_random.c в коде mozilla. (предположим, что это mozilla/security/freebl/...) он должен быть в библиотеке freebl.

там он использует информацию о системном вызове (например, pwd, netstat....) для генерации шума для случайного числа, он написан для поддержки большинства платформ (которые могут получить мне бонусную точку: D).

Ответ 8

Реальный вопрос, который вы должны задать себе, - это качество случайности, которое вам нужно.

libc random - LCG

Качество случайности будет низким, независимо от того, какой вклад вы предоставляете srand.

Если вам просто нужно убедиться, что разные экземпляры будут иметь разные инициализации, вы можете смешать идентификатор процесса (getpid), идентификатор потока и таймер. Смешайте результаты с xor. Энтропия должна быть достаточной для большинства приложений.

Пример:

struct timeb tp;
ftime(&tp);   
srand(static_cast<unsigned int>(getpid()) ^ 
static_cast<unsigned int>(pthread_self()) ^ 
static_cast<unsigned int >(tp.millitm));

Для лучшего случайного качества используйте /dev/urandom. Вы можете сделать приведенный выше код переносимым с помощью boost:: thread и boost:: date_time.

Ответ 9

Версия c++11 верхней голосовой почты Джонатана Райт:

#include <ctime>
#include <random>
#include <thread>

...

const auto time_seed = static_cast<size_t>(std::time(0));
const auto clock_seed = static_cast<size_t>(std::clock());
const size_t pid_seed =
      std::hash<std::thread::id>()(std::this_thread::get_id());

std::seed_seq seed_value { time_seed, clock_seed, pid_seed };

...
// E.g seeding an engine with the above seed.
std::mt19937 gen;
gen.seed(seed_value);

Ответ 10

#include <stdio.h>
#include <sys/time.h>
main()
{
     struct timeval tv;
     gettimeofday(&tv,NULL);
     printf("%d\n",  tv.tv_usec);
     return 0;
}

tv.tv_usec находится в микросекундах. Это должно быть приемлемым семени.

Ответ 11

Предположим, что у вас есть функция с сигнатурой вроде:

int foo(char *p);

Отличным источником энтропии для случайного семени является хэш следующего:

  • Полный результат clock_gettime (секунды и наносекунды), не отбрасывая низкие бит - они самые ценные.
  • Значение p, отличное от uintptr_t.
  • Адрес p, отбрасывается на uintptr_t.

По крайней мере, третий, а возможно и второй, выводит энтропию из ASLR системы, если она доступна (исходный адрес стека и, следовательно, текущий адрес стека, несколько случайны).

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

Ответ 12

Для тех, кто использует Visual Studio, еще один способ:

#include "stdafx.h"
#include <time.h>
#include <windows.h> 

const __int64 DELTA_EPOCH_IN_MICROSECS= 11644473600000000;

struct timezone2 
{
  __int32  tz_minuteswest; /* minutes W of Greenwich */
  bool  tz_dsttime;     /* type of dst correction */
};

struct timeval2 {
__int32    tv_sec;         /* seconds */
__int32    tv_usec;        /* microseconds */
};

int gettimeofday(struct timeval2 *tv/*in*/, struct timezone2 *tz/*in*/)
{
  FILETIME ft;
  __int64 tmpres = 0;
  TIME_ZONE_INFORMATION tz_winapi;
  int rez = 0;

  ZeroMemory(&ft, sizeof(ft));
  ZeroMemory(&tz_winapi, sizeof(tz_winapi));

  GetSystemTimeAsFileTime(&ft);

  tmpres = ft.dwHighDateTime;
  tmpres <<= 32;
  tmpres |= ft.dwLowDateTime;

  /*converting file time to unix epoch*/
  tmpres /= 10;  /*convert into microseconds*/
  tmpres -= DELTA_EPOCH_IN_MICROSECS; 
  tv->tv_sec = (__int32)(tmpres * 0.000001);
  tv->tv_usec = (tmpres % 1000000);


  //_tzset(),don't work properly, so we use GetTimeZoneInformation
  rez = GetTimeZoneInformation(&tz_winapi);
  tz->tz_dsttime = (rez == 2) ? true : false;
  tz->tz_minuteswest = tz_winapi.Bias + ((rez == 2) ? tz_winapi.DaylightBias : 0);

  return 0;
}


int main(int argc, char** argv) {

  struct timeval2 tv;
  struct timezone2 tz;

  ZeroMemory(&tv, sizeof(tv));
  ZeroMemory(&tz, sizeof(tz));

  gettimeofday(&tv, &tz);

  unsigned long seed = tv.tv_sec ^ (tv.tv_usec << 12);

  srand(seed);

}

Возможно, немного перебор, но хорошо работает для быстрых интервалов. Функция gettimeofday нашла здесь.

Изменить: при дальнейшем исследовании rand_s может быть хорошей альтернативой для Visual Studio, это не просто безопасный rand(), он совершенно другой и не использует семя из srand. Я предположил, что это почти идентично ранду, просто "безопасному".

Чтобы использовать rand_s, просто не забудьте указать #define _CRT_RAND_S перед включением stdlib.h.

Ответ 13

Пока ваша программа работает только в Linux (и ваша программа является исполняемым файлом ELF), вы гарантированно, что ядро ​​предоставляет вашему процессу уникальное случайное семя в векторном элементе ELF. Ядро дает вам 16 случайных байтов, разных для каждого процесса, которые вы можете получить с помощью getauxval(AT_RANDOM). Чтобы использовать их для srand, используйте только int из них:

#include <sys/auxv.h>

void initrand(void)
{
    unsigned int *seed;

    seed = (unsigned int *)getauxval(AT_RANDOM);
    srand(*seed);
}

Возможно, это также переводит на другие системы на базе ELF. Я не уверен, какие значения aux реализованы в системах, отличных от Linux.

Ответ 14

Предполагая, что случайности srand() + rand() достаточно для ваших целей, хитрость заключается в выборе лучшего семени для srand. время (NULL) является хорошей отправной точкой, но у вас возникнут проблемы, если вы запустите более одного экземпляра программы в течение одной секунды. Добавление pid (идентификатора процесса) является улучшением, поскольку разные экземпляры получают разные pids. Я бы умножил pid на коэффициент, чтобы распространять их больше.

Но допустим, вы используете это для какого-то встроенного устройства, и у вас есть несколько в одной сети. Если все они включаются одновременно, и вы запускаете несколько экземпляров вашей программы автоматически во время загрузки, они все равно могут получать одинаковое время и pid, и все устройства будут генерировать одинаковую последовательность "случайных" чисел. В этом случае вам может потребоваться добавить некоторый уникальный идентификатор каждого устройства (например, серийный номер ЦП).

Предложенная инициализация будет:

srand(time(NULL) + 1000 * getpid() + (uint) getCpuSerialNumber()); 

На компьютере с Linux (по крайней мере, в Raspberry Pi, где я это тестировал) вы можете реализовать следующую функцию для получения серийного номера ЦП:

// Gets the CPU Serial Number as a 64 bit unsigned int. Returns 0 if not found.
uint64_t getCpuSerialNumber() {

    FILE *f = fopen("/proc/cpuinfo", "r");
    if (!f) {
        return 0;
    }

    char line[256];
    uint64_t serial = 0;
    while (fgets(line, 256, f)) {
        if (strncmp(line, "Serial", 6) == 0) {
            serial = strtoull(strchr(line, ':') + 2, NULL, 16);
        }
    }
    fclose(f);

    return serial;
}

Ответ 15

Включите заголовок в верхней части вашей программы и напишите:

srand(time(NULL));

В вашей программе перед объявлением вашего случайного числа. Вот пример программы, которая печатает случайное число от одного до десяти:

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
   //Initialize srand
   srand(time(NULL));

   //Create random number
   int n = rand() % 10 + 1;

   //Print the number
   cout << n << endl; //End the line

   //The main function is an int, so it must return a value
   return 0;
}