Подтвердить что ты не робот

Как вы генерируете случайный двойной равномерно распределенный между 0 и 1 из С++?

Как вы производите случайный двойной равномерно распределенный между 0 и 1 из С++?

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

  • Хорошее соответствие стандартам
  • Хорошая случайность
  • Хорошая скорость

(скорость важнее случайности для моего приложения).

Спасибо большое!

PS: В этом случае мои целевые платформы - это Linux и Windows.

4b9b3361

Ответ 1

В С++ 11 и С++ 14 у нас есть намного лучшие варианты с случайным заголовком. Презентация rand() считается вредной Стефаном Т. Лававием объясняет, почему мы должны избегать использования rand() в С++ в пользу заголовка random и N3924: Отказ от rand() в С++ 14 еще больше усиливает эту точку.

Пример ниже - это модифицированная версия примерного кода на сайте cppreference и использует движок std:: mersenne_twister_engine и std:: uniform_real_distribution, который генерирует числа в диапазоне [0,1) (см. его в прямом эфире):

#include <iostream>
#include <iomanip>
#include <map>
#include <random>

int main()
{
    std::random_device rd;


    std::mt19937 e2(rd());

    std::uniform_real_distribution<> dist(0, 1);

    std::map<int, int> hist;
    for (int n = 0; n < 10000; ++n) {
        ++hist[std::round(dist(e2))];
    }

    for (auto p : hist) {
        std::cout << std::fixed << std::setprecision(1) << std::setw(2)
                  << p.first << ' ' << std::string(p.second/200, '*') << '\n';
    }
}

будет выглядеть примерно так:

0 ************************
1 *************************

Поскольку сообщение упоминало, что скорость важна, мы должны рассмотреть раздел cppreference, который описывает разные двигатели случайных чисел (выделение мое):

Выбор того, какой движок использовать , включает в себя ряд компромиссов *: ** линейный конгруэнтный двигатель умеренно быстр и имеет очень малую требование хранения для состояния. отсталые генераторы Фибоначчи очень быстро даже на процессорах без расширенной арифметической командыза счет большего государственного хранения, а иногда и меньше желательные спектральные характеристики. Mersenne twister работает медленнее и имеет более высокие требования к хранению данных, но с правильными параметрами имеет самую длинную не повторяющуюся последовательность с наиболее желательной спектральные характеристики (для заданного определения желаемого).

Итак, если есть желание для более быстрого генератора, возможно, ranlux24_base или ranlux48_base - лучший выбор по сравнению с mt19937.

Rand()

Если вы вынуждены использовать rand(), то C FAQ для руководства по Как я могу генерировать случайные числа с плавающей запятой?, дает нам пример, аналогичный это для генерации a на интервале [0,1):

#include <stdlib.h>

double randZeroToOne()
{
    return rand() / (RAND_MAX + 1.);
}

и создать случайное число в диапазоне от [M,N):

double randMToN(double M, double N)
{
    return M + (rand() / ( RAND_MAX / (N-M) ) ) ;  
}

Ответ 2

Старое школьное решение вроде:

double X=((double)rand()/(double)RAND_MAX);

Соответствует всем вашим критериям (портативным, стандартным и быстрым). очевидно, что генерируемое случайное число должно быть затрачено, стандартная процедура - это что-то вроде:

srand((unsigned)time(NULL));

Ответ 4

Вот как вы это сделали бы, если бы использовали С++ TR1.

Ответ 5

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

double r = (double)rand() / (double)RAND_MAX;

Ответ 6

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

Однако для кода производственного класса вы должны точно знать, каковы конкретные свойства различных генераторов, прежде чем вы их используете, поскольку все они имеют свои оговорки. Кроме того, ни один из них не пропускает стандартные тесты для PRNG, например TestU01, за исключением генераторов ranlux, если они используются с великодушным фактором роскоши.

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

Если вам нужна переносимость, тогда вы должны принести свой собственный генератор.

Если вы можете жить с ограниченной переносимостью, вы можете использовать boost или С++ 11 в сочетании с вашим собственным генератором.

Более подробная информация - включая код для простого, но быстрого генератора отличного качества и обширных ссылок - можно найти в моих ответах на похожие темы:

Для профессиональных унифицированных с плавающей запятой есть еще два вопроса:

  • открыто против полуоткрытого по отношению к замкнутому диапазону, то есть (0,1), [0, 1) или [0,1]
  • метод преобразования от интегральной к плавающей точке (точность, скорость)

Оба являются фактически двумя сторонами одной и той же монеты, так как метод преобразования заботится о включении/исключении 0 и 1. Вот три разных метода для полуоткрытого интервала:

// exact values computed with bc

#define POW2_M32   2.3283064365386962890625e-010
#define POW2_M64   5.421010862427522170037264004349e-020

double random_double_a ()
{
   double lo = random_uint32() * POW2_M64;
   return lo + random_uint32() * POW2_M32;
}

double random_double_b ()
{
   return random_uint64() * POW2_M64;
}

double random_double_c ()
{
   return int64_t(random_uint64()) * POW2_M64 + 0.5;
}

(random_uint32() и random_uint64() являются заполнителями для ваших фактических функций и обычно передаются как параметры шаблона)

Метод a демонстрирует, как создать равномерное отклонение, которое не подвержено избыточной точности для более низких значений; код для 64-битного не показан, потому что он проще и просто включает маскировку 11 бит. Распределение является однородным для всех функций, но без этого трюка в области, расположенной ближе к 0, будут более разные значения (более мелкие интервалы сетки из-за различной ulp).

Метод c показывает, как получить равномерное отклонение быстрее на некоторых популярных платформах, где FPU знает только подписанный 64-битный интегральный тип. Наиболее часто вы видите метод b, но там компилятор должен генерировать много дополнительного кода под капотом для сохранения семантики без знака.

Смешайте и совместите эти принципы, чтобы создать собственное индивидуальное решение.

Все это объясняется в Jürgen Doornik превосходной бумаге Преобразование случайных чисел с высоким периодом в плавучую точку.

Ответ 7

Сначала включите stdlib.h

#include<stdlib.h>

Тогда следующим может быть функция генерации случайного двойного числа между диапазоном в языке программирования C.

double randomDouble() {
    double lowerRange = 1.0;
    double upperRange = 10.0;
    return ((double)rand() * (upperRange - lowerRange)) / (double)RAND_MAX + lowerRange;
}

Здесь RAND_MAX определяется в stdlib.h

Ответ 8

Как я вижу, есть три способа сделать это,

1) Простой способ.

double rand_easy(void)
{       return (double) rand() / (RAND_MAX + 1.0);
}

2) Безопасный путь (стандартное соответствие).

double rand_safe(void)
{
        double limit = pow(2.0, DBL_MANT_DIG);
        double denom = RAND_MAX + 1.0;
        double denom_to_k = 1.0;
        double numer = 0.0;

        for ( ; denom_to_k < limit; denom_to_k *= denom )
           numer += rand() * denom_to_k;

        double result = numer / denom_to_k;
        if (result == 1.0)
           result -= DBL_EPSILON/2;
        assert(result != 1.0);
        return result;
}

3) Пользовательский способ.

Исключив rand(), нам больше не нужно беспокоиться об особенностях какой-либо конкретной версии, что дает нам большую свободу действий в нашей собственной реализации.

Примечание: Период использования генератора здесь: & cong; 1.8e + 19.

#define RANDMAX (-1ULL)
uint64_t custom_lcg(uint_fast64_t* next)
{       return *next = *next * 2862933555777941757ULL + 3037000493ULL;
}

uint_fast64_t internal_next;
void seed_fast(uint64_t seed)
{       internal_next = seed;
}

double rand_fast(void)
{
#define SHR_BIT (64 - (DBL_MANT_DIG-1))
        union {
            double f; uint64_t i;
        } u;
        u.f = 1.0;
        u.i = u.i | (custom_lcg(&internal_next) >> SHR_BIT);
        return u.f - 1.0;
}

Независимо от выбора, функциональность может быть расширена следующим образом:

double rand_dist(double min, double max)
{       return rand_fast() * (max - min) + min;
}

double rand_open(void)
{       return rand_dist(DBL_EPSILON, 1.0);
}

double rand_closed(void)
{       return rand_dist(0.0, 1.0 + DBL_EPSILON);
}

Заключительные примечания: Быстрая версия - при написании на C - может быть адаптирована для использования в С++ для замены в качестве замены для std::generate_canonical и будет работать для любого генератора, испускающего значения с достаточным Значимые бит.

Большинство 64-битных генераторов используют их полную ширину, поэтому их можно использовать без изменений (регулировка сдвига). например это работает как есть с движком std::mt19937_64.

Ответ 9

Хорошо учитывая простоту и скорость в качестве ваших основных критериев, вы можете добавить небольшой общий помощник, например:

  // C++ rand generates random numbers between 0 and RAND_MAX. This is quite a big range
  // Normally one would want the generated random number within a range to be really
  // useful. So the arguments have default values which can be overridden by the caller
  int nextRandomNum(int low = 0, int high = 100) const {
    int range = (high - low) + 1;
    // this modulo operation does not generate a truly uniformly distributed random number
    // in the span (since in most cases lower numbers are slightly more likely), 
    // but it is generally a good approximation for short spans. Use it if essential
    //int res = ( std::rand() % high + low );
    int res = low + static_cast<int>( ( range * std::rand() / ( RAND_MAX + 1.0) ) );
    return res;
  }

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

Eternally Confuzzled

Ответ 10

Вы можете попробовать алгоритм Mersenne Twister.

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

Он имеет хорошее сочетание скорости и случайности, а также реализацию GPL.

Ответ 11

Это то, что я использовал для своих нужд:

int range_upper_bound = 12345;
int random_number =((double)rand()/(double)range_upper_bound);

Ответ 12

double randDouble()
{
  double out;
  out = (double)rand()/(RAND_MAX + 1); //each iteration produces a number in [0, 1)
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;
  out = (rand() + out)/RAND_MAX;

  return out;
}

Не так быстро, как double X=((double)rand()/(double)RAND_MAX);, но с лучшим распределением. Этот алгоритм дает только RAND_MAX равномерно распределенный выбор возвращаемых значений; это дает RANDMAX ^ 6, поэтому его распределение ограничено только двойной точностью.

Если вы хотите длинный двойной, просто добавьте несколько итераций. Если вы хотите, чтобы число в [0, 1], а не [0, 1), просто введите строку 4, прочитав out = (double)rand()/(RAND_MAX);.

Ответ 13

//Returns a random number in the range (0.0f, 1.0f).
// 0111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
// seee eeee eeee vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv vvvv
// sign     = 's'
// exponent = 'e'
// value    = 'v'
double DoubleRand() {
  typedef unsigned long long uint64;
  uint64 ret = 0;
  for (int i = 0; i < 13; i++) {
     ret |= ((uint64) (rand() % 16) << i * 4);
  }
  if (ret == 0) {
    return rand() % 2 ? 1.0f : 0.0f;
  }
  uint64 retb = ret;
  unsigned int exp = 0x3ff;
  retb = ret | ((uint64) exp << 52);
  double *tmp = (double*) &retb;
  double retval = *tmp;
  while (retval > 1.0f || retval < 0.0f) {
    retval = *(tmp = (double*) &(retb = ret | ((uint64) (exp--) << 52)));
  }
  if (rand() % 2) {
    retval -= 0.5f;
  }
  return retval;
}

Это должно сделать трюк, я использовал this статью Википедии, чтобы помочь в создании этого. Я считаю, что это так же хорошо, как drand48();