C ++ Получить случайное число от 0 до максимального длинного целого числа

0

У меня есть следующая функция:

typedef unsigned long long int UINT64;
UINT64 getRandom(const UINT64 &begin = 0, const UINT64 &end = 100) {
    return begin >= end ? 0 : begin + (UINT64) ((end - begin)*rand()/(double)RAND_MAX);
};

Когда я звоню

getRandom(0, ULLONG_MAX);

или

getRandom(0, LLONG_MAX);

Я всегда получаю то же значение 562967133814800. Как я могу исправить эту проблему?

  • 0
    Ваша функция rand() недостаточно точна для вашего случая использования.
  • 0
    Учитывая, что по крайней мере одна широко распространенная реализация стандартной библиотеки C имеет 32767 как RAND_MAX , я бы не стал распространять rand() на 64-битное пространство ...
Показать ещё 4 комментария
Теги:

8 ответов

14
Лучший ответ

Что такое rand()?

В соответствии с этим функция rand() возвращает значение в диапазоне [0,RAND_MAX].

Что такое RAND_MAX?

В соответствии с этим RAND_MAX является "интегральным постоянным выражением, значение которого является максимальным значением, возвращаемым функцией rand. Это значение зависит от библиотеки, но гарантировано, что оно равно по меньшей мере 32767 для любой стандартной реализации библиотеки".

Точность - проблема

Вы берете rand()/(double)RAND_MAX, но у вас есть, возможно, только 32767 дискретных значений. Таким образом, хотя у вас есть большие числа, на самом деле у вас больше нет чисел. Это может быть проблемой.

Посев может стать проблемой

Кроме того, вы не говорите о том, как вы вызываете функцию. Вы запускаете программу один раз с LLONG_MAX и в другой раз с ULLONG_MAX? В этом случае поведение, которое вы видите, связано с тем, что вы неявно используете одно и то же случайное семя каждый раз. Иными словами, каждый раз, когда вы запускаете программу, он генерирует ту же последовательность случайных чисел.

Как я могу посеять?

Вы можете использовать srand() следующим образом:

#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */

int main (){
  srand (time(NULL));
  //The rest of your program goes here
}

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

Переполнение - проблема

Рассмотрим эту часть ((end - begin)*rand()/(double)RAND_MAX).

Что такое (end-begin)? Это LLONG_MAX или ULLONG_MAX это, по определению, самые большие возможные значения, которые могут хранить эти типы данных. Поэтому было бы плохим умножить их на что угодно. Но вы делаете! Вы умножаете их на rand(), который отличен от нуля. Это приведет к переполнению. Но мы можем исправить это...

Порядок операций - это проблема

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

((end - begin) * (rand()/(double)RAND_MAX) )

Обратите внимание на новые скобки! ( rand()/(double)RAND_MAX )

Теперь вы умножаете целое на дробь, поэтому вам гарантировано не переполнение. Но это вводит новую проблему...

Продвижение - проблема

Но есть еще более глубокая проблема. Вы разделите int на double. Когда вы делаете это, int повышается до double. Двойной номер с плавающей запятой, что в основном означает, что он жертвует точностью, чтобы иметь большой диапазон. Возможно, это вас кусает. По мере того, как вы получаете все больше и больше, ваши ullong и ваш llong конечном итоге получают llong. Это может быть особенно актуально, если вы сначала переполнили свой тип данных (см. Выше).

Ох о

Итак, в основном, все о PRNG, которое вы представили, неверно.

Возможно, именно поэтому Джон фон Нейман сказал

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

И иногда мы платим за эти грехи.

Как я могу освободиться?

С++ 11 обеспечивает отличную функциональность. Вы можете использовать его следующим образом

#include <iostream>
#include <random>
#include <limits>
int main(){
  std::random_device rd;     //Get a random seed from the OS entropy device, or whatever
  std::mt19937_64 eng(rd()); //Use the 64-bit Mersenne Twister 19937 generator
                             //and seed it with entropy.

  //Define the distribution, by default it goes from 0 to MAX(unsigned long long)
  //or what have you.
  std::uniform_int_distribution<unsigned long long> distr;

  //Generate random numbers
  for(int n=0; n<40; n++)
    std::cout << distr(eng) << ' ';
  std::cout << std::endl;
}

(Обратите внимание, что надлежащим образом высевать генератор сложно. Этот вопрос затрагивает это.)

  • 1
    Я уже посеял это. И я получаю одно и то же значение каждый раз, когда я вызываю функцию в этом диапазоне
  • 0
    Смотрите мой отредактированный ответ @ NguyễnĐứcLong.
Показать ещё 2 комментария
2
typedef unsigned long long int UINT64;

UINT64 getRandom(UINT64 const& min = 0, UINT64 const& max = 0)
{
    return (((UINT64)(unsigned int)rand() << 32) + (UINT64)(unsigned int)rand()) % (max - min) + min;
}

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

unsigned __int64 getRandom(unsigned __int64 const& min, unsigned __int64 const& max)
{
    return (((unsigned __int64)(unsigned int)rand() << 32) + (unsigned __int64)(unsigned int)rand()) % (max - min) + min;
}
  • 0
    При условии, что RAND_MAX имеет ширину 32 бита.
  • 0
    @ddriver при условии, что int имеет ширину 32 бита и длинную длину 64 бита :)
Показать ещё 6 комментариев
1

Учитывая, что ULLONG_MAX и LLONG_MAX оба больше, чем значение RAND_MAX, вы наверняка получите "меньше точности, чем хотите".

Кроме того, вероятность 50% ниже, чем у LLONG_MAX, так как она находится на полпути к диапазону 64-битных значений.

Я бы предложил использовать Mersenne-Twister из С++ 11, который имеет 64-битный вариант http://www.cplusplus.com/reference/random/mt19937_64/

Это должно дать вам значение, которое соответствует 64-битовому числу.

Если вы "всегда получаете одно и то же значение", то это потому, что вы не заселили генератор случайных чисел, используя, например, srand(time(0)) - вы обычно должны только посещать один раз, потому что это устанавливает "последовательность". Если семя очень похоже, например, вы запускаете одну и ту же программу дважды в короткой последовательности, вы все равно получите одну и ту же последовательность, потому что "время" только тикает один раз в секунду, и даже тогда это не так сильно изменится. Существуют различные другие способы заселения случайного числа, но для большинства целей time(0) является достаточно хорошим.

  • 0
    Это правда, что ULLONG_MAX и LLONG_MAX больше, чем RAND_MAX . Я всегда получаю один и тот же результат всякий раз, когда вызываю функцию в этом диапазоне. Не говоря о точности, почему я получаю тот же результат, который меньше желаемого диапазона?
  • 0
    Я думаю, что вы напечатали это до того, как увидели мою правку
Показать ещё 3 комментария
1

Используйте свой собственный PRNG, который соответствует вашим требованиям, а не тот, который предоставляется с rand, которого, похоже, не было и никогда не было гарантировано.

1

Вы переполняете вычисление, в выражении

((end - begin)*rand()/(double)RAND_MAX)

вы сообщаете компилятору размножаться (ULLONG_MAX - 0) * rand(), а затем делятся на RAND_MAX, сначала нужно разделить RAND_MAX, а затем умножить на rand().

// http://stackoverflow.com/questions/22883840/c-get-random-number-from-0-to-max-long-long-integer
#include <iostream>
#include <stdlib.h>     /* srand, rand */
#include <limits.h>
using std::cout;
using std::endl;

typedef unsigned long long int UINT64;
UINT64 getRandom(const UINT64 &begin = 0, const UINT64 &end = 100) {
    //return begin >= end ? 0 : begin + (UINT64) ((end - begin)*rand()/(double)RAND_MAX);
    return begin >= end ? 0 : begin + (UINT64) rand()*((end - begin)/RAND_MAX);
};

int main( int argc, char *argv[] )
{
    cout << getRandom(0, ULLONG_MAX) << endl;
    cout << getRandom(0, ULLONG_MAX) << endl;
    cout << getRandom(0, ULLONG_MAX) << endl;
    return 0;
}

Смотреть онлайн в Колиру

0

Термин (end - begin)*rand() видимому, вызывает переполнение. Вы можете решить эту проблему, используя (end - begin) * (rand()/(double)RAND_MAX). Используя второй способ, я получаю следующие результаты:

15498727792227194880
7275080918072332288
14445630964995612672
14728618955737210880

со следующими вызовами:

std::cout << getRandom(0, ULLONG_MAX) << std::endl;
std::cout << getRandom(0, ULLONG_MAX) << std::endl;
std::cout << getRandom(0, ULLONG_MAX) << std::endl;
std::cout << getRandom(0, ULLONG_MAX) << std::endl;
0
union bigRand {
    uint64_t ll;
    uint32_t ii[2];   
};

uint64_t rand64() {
    bigRand b;
    b.ii[0] =  rand();
    b.ii[1] =  rand();
    return b.ll;
}

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

0

Наиболее разумным решением было бы использовать С++ 11 <random>, mt19937_64.

Альтернативно вы можете попробовать:

return ((double)rand() / ((double)RAND_MAX + 1.0)) * (end - begin + 1) + begin;

чтобы производить цифры более разумным образом. Однако обратите внимание, что, как и ваша первая попытка, это все равно не будет производить равномерно распределенные номера (хотя это может быть и достаточно хорошо).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню