rand() каждый раз дает почти одно и то же число

Я изучаю C и хочу сгенерировать число от 0 до 6400. Вот код, который я придумал:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand(time(0));
    int i = (rand() % 6401);
    printf("Random number between 0 and 6400: %d\n", i);
    return 0;
}

Когда я компилирую и запускаю этот код из командной строки, я получаю очень странные результаты:

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6282

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6282

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6285

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6285

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6289

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6289

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6292

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6292

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6295

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6298

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6298

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6302

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6302

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6305

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6305

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6308

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6308

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6311

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6311

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6315

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6315

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6318

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6318

K:\C\Labo\Oefeningen 2019>a
Random number between 0 and 6400: 6321

K:\C\Labo\Oefeningen 2019>

Цифры все разные, но я ожидаю более равномерного распределения между 0 и 6400. Странно то, что я без проблем использовал ту же функцию час назад? (Раньше я использовал его для генерации меньших чисел.) Я уверен, что это что-то действительно глупое, чего мне не хватает, но я застрял уже на час.

РЕДАКТИРОВАТЬ: я знаю, что это даст то же значение, когда вы запустите код в течение той же секунды. Я ждал несколько секунд (10-20) между выполнениями и все равно получаю тот же результат? Значения редко бывают одинаковыми, они просто очень-очень похожи в 100% случаев. Как мне обойти это?


person Zwarte Kop    schedule 28.09.2019    source источник
comment
time(0) возвращает количество секунд, прошедших с 1970 года. Его значение должно меняться каждую секунду. Поэтому, если вы запустите эту программу более одного раза в секунду, вы можете получить одинаковые результаты. Но по прошествии времени значения не должны быть одинаковыми. Вы можете добавить вывод этой функции в консоль для сравнения. Если вам нужны доли секунды, вы можете использовать gettime, для которого в качестве параметра требуется указатель структуры.   -  person Andrew    schedule 29.09.2019


Ответы (5)


Числа, сгенерированные rand, на самом деле не случайны, они сгенерированы по формуле. Поэтому посев возможен и необходим. В зависимости от используемой формулы может быть высокая корреляция между начальным числом и несколькими первыми случайными числами.

Лекарства заключаются в том, чтобы использовать лучшую формулу (что-то не rand), использовать более случайное начальное число или тратить несколько случайных чисел сразу после начального числа.

person Mark Ransom    schedule 30.09.2019

time() имеет разрешение 1 секунду. Таким образом, ваша программа будет генерировать другое значение только после того, как в среднем пройдет полсекунды.

Если ваш компилятор поддерживает C11, вы можете использовать функцию с более высоким разрешением, timespec_get(). Затем ваш srand(time(0)); превратится в следующее:

struct timespec ts;
timespec_get(&ts, TIME_UTC);
srand(ts.tv_nsec);

Здесь ts.tv_nsec — наносекундная часть метки времени, разрешение которой должно быть достаточно хорошим для ваших целей.

Если ваш компилятор не поддерживает C11, у вас все равно может быть лучший источник случайного начального числа, чем time(), с разрешением около миллисекунды (фактическое разрешение задается макросом CLOCKS_PER_SEC): функция clock(). Тогда ваш код заполнения будет

srand(clock());

Однако обратите внимание, что на самом деле это может быть плохим источником энтропии, особенно если ваша ОС не занята, поэтому программа будет работать с несколько предсказуемой скоростью. Потому что начало эры clock() связано с выполнением программы, а не с реальным временем. Может быть лучше, например. используйте сумму clock() и time(0), чтобы получить более непредсказуемое значение:

srand(time(0)+clock());
person Ruslan    schedule 28.09.2019
comment
Я ждал несколько секунд между выполнением, но значение по-прежнему находится между 6280 и 3000. Чтобы увидеть какие-либо реальные изменения, мне нужно подождать несколько минут. Почему это? - person Zwarte Kop; 29.09.2019
comment
@ZwarteKop Возможно, реализация srand и rand в вашем компиляторе слишком плоха. Попробуйте запустить srand((unsigned)rand()*rand()) после первого srand: это может дать лучшие результаты, хотя я не знаю, даст ли это. - person Ruslan; 29.09.2019
comment
Хорошая идея с частью ts.tv_nsec; это будет по существу совершенно случайным и проходит полный цикл каждые 1 с. Вы можете рассмотреть возможность добавления getpid() к этому, если возникнет нишевый случай. - person S.S. Anne; 29.09.2019
comment
@ JL2210 это POSIX, а не чистый C. - person Ruslan; 29.09.2019

Компьютеры на самом деле не генерируют случайные числа. Таким образом, когда вы выполняете свой код дважды в одну и ту же секунду, он возвращает одно и то же значение. Чтобы получить лучший результат, вы можете добавить значение getpid() в srand.

Имейте в виду, что это все еще не настоящий случайный выбор.

person elkolotfi    schedule 28.09.2019
comment
Да, но он также возвращает почти идентичное значение, когда я жду более 30 секунд. - person Zwarte Kop; 29.09.2019

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

Вот пример реализации: я использовал этот one в качестве ссылки.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>

uint64_t    xorshift64s(uint64_t seed)
{
    static uint64_t i = 1;

    if (seed != 0)
        i = seed;
    i ^= i >> 12;
    i ^= i << 25;
    i ^= i >> 27;
    i *= 0x2545f4914f6cdd1d;
    return (i >> 32);
}


int main()
{
    srand(time(0));
    int i = (rand() % 6401);
    printf("rand    : Random number between 0 and 6400: %d\n", i);
    xorshift64s(time(0));
    int j = (xorshift64s(0) % 6401);
    printf("xorshift: Random number between 0 and 6400: %d\n", j);
    return 0;
}
person AugustinLopez    schedule 29.09.2019

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

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    srand((unsigned int)time(0) * 100000000);
    int i = (rand() % 6401);
    printf("Random number between 0 and 6400: %d\n", i);
    return 0;
}

Здесь я получил свой компилятор мусорной корзины, кстати: http://tdm-gcc.tdragon.net/download

person Zwarte Kop    schedule 29.09.2019