Как вы генерируете случайный дубль, равномерно распределенный между 0 и 1 из C ++? - PullRequest
58 голосов
/ 27 августа 2009

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

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

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

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

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

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

Ответы [ 13 ]

55 голосов
/ 27 августа 2009

Решение старой школы, например:

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

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

srand((unsigned)time(NULL));
31 голосов
/ 11 ноября 2014

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

Пример, приведенный ниже, представляет собой измененную версию примера кода на сайте cppreference и использует механизм std :: mersenne_twister_engine и std ::iform_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(), тогда C FAQ для руководства по Как я могу генерировать случайные числа с плавающей запятой? , дает нам пример, подобный этому для генерирование на интервале [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) ) ) ;  
}
6 голосов
/ 27 августа 2009

Класс random_real из библиотеки Boost random - это то, что вам нужно.

5 голосов
/ 27 августа 2009

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

3 голосов
/ 11 ноября 2014

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

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

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

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

Если вы можете жить с ограниченной переносимостью, тогда вы можете использовать boost или C ++ 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 , но там компилятор должен генерировать много дополнительного кода под капотом, чтобы сохранить семантику без знака.

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

Все это объясняется в превосходной статье Юргена Доорника Преобразование случайных чисел высокого периода в число с плавающей точкой .

3 голосов
/ 27 августа 2009

Если скорость - ваша главная задача, то я бы просто пошел с

double r = (double)rand() / (double)RAND_MAX;
2 голосов
/ 30 сентября 2015

Сначала включите 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

1 голос
/ 07 апреля 2015

На мой взгляд, есть три способа сделать это,

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. * * 1016

#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 - может быть адаптирована для использования в C ++ для замены std::generate_canonical и будет работать для любого генератора, генерирующего значения с достаточно значительными бит.

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

0 голосов
/ 09 марта 2017

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

int range_upper_bound = 12345;
int random_number =((double)rand()/(double)range_upper_bound);
0 голосов
/ 09 сентября 2011
//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;
}

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

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