У меня есть следующая функция:
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
. Как я могу исправить эту проблему?
Что такое 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;
}
(Обратите внимание, что надлежащим образом высевать генератор сложно. Этот вопрос затрагивает это.)
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;
}
Учитывая, что 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)
является достаточно хорошим.
ULLONG_MAX
и LLONG_MAX
больше, чем RAND_MAX
. Я всегда получаю один и тот же результат всякий раз, когда вызываю функцию в этом диапазоне. Не говоря о точности, почему я получаю тот же результат, который меньше желаемого диапазона?
Используйте свой собственный PRNG, который соответствует вашим требованиям, а не тот, который предоставляется с rand
, которого, похоже, не было и никогда не было гарантировано.
Вы переполняете вычисление, в выражении
((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;
}
Смотреть онлайн в Колиру
Термин (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;
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 находится на конкретной платформе. Как потенциал роста, это жестоко просто. Я имею в виду, что компилятор, скорее всего, оптимизирует его, чтобы быть достаточно эффективным, без дополнительной арифметики. Просто расходы на вызов рэнд дважды.
Наиболее разумным решением было бы использовать С++ 11 <random>
, mt19937_64
.
Альтернативно вы можете попробовать:
return ((double)rand() / ((double)RAND_MAX + 1.0)) * (end - begin + 1) + begin;
чтобы производить цифры более разумным образом. Однако обратите внимание, что, как и ваша первая попытка, это все равно не будет производить равномерно распределенные номера (хотя это может быть и достаточно хорошо).
rand()
недостаточно точна для вашего случая использования.RAND_MAX
, я бы не стал распространятьrand()
на 64-битное пространство ...