Rand 0 ... Max Int с равномерным распределением

23 апреля 2020, 10:53
Forester
3
212

Потребовалась функция ГСЧ с гнерацией числа Int от 0 до любого значения.

Получилась такая функция. Думаю распределение получилось равномерным.

int RandomInteger(int max_vl){return (int)MathFloor((MathRand()+MathRand()*32767.0)/1073741824.0*max_vl);}//случайное Int от 0 до  1073741824


На форуме посоветовали другую функцию из статьи. Отбросив лишнее, получилось:

//если из define переместить в код RNDUint, то скорость работы увеличится на 30% для 10 млн повторов с 600 мс до 850 мс
#define xor32  xx=xx^(xx<<13);xx=xx^(xx>>17);xx=xx^(xx<<5)
#define xor128 t=(x^(x<<11));x=y;y=z;z=w;w=(w^(w>>19))^(t^(t>>8))
#define inidat x=123456789;y=362436069;z=521288629;w=88675123;xx=2463534242

class RNDUint{
protected:
   uint      x,y,z,w,xx,t;
public:
        RNDUint(void){inidat;};
        ~RNDUint(void){};
   uint      Rand()    {xor128;return(w);};//равномерное распределение на отрезке [0,UINT_MAX=4294967295].
   double    Rand_01() {xor128;return((double)w/UINT_MAX);};//равномерное распределение на отрезке [0,1].
   void      Reset()   {inidat;};//сброс всех исходных значений в первоначальное состояние.
   void      SRand(uint seed)  {//установка новых исходных значений генератора.seed= [0,UINT_MAX=4294967295]. При seed=0 функция меняет начальные значения случайным образом.
      int i;if(seed!=0){xx=seed;}for(i=0;i<16;i++){xor32;}xor32;x=xx;xor32;y=xx;xor32;z=xx;xor32;w=xx;for(i=0;i<16;i++){xor128;}
   };
};

Сделал сравнение по скорости обоих функций, оригинальной из статьи и просто MathRand():


Оригинальная из статьи Rnd.Rand_01() - 602 мс
Укороченная по статье rnu.Rand_01() - 596 мс
Моя первоначальная версия RandomInteger()  - 840 мс
Просто по MathRand() - 353 мс (распределение будет равномерным до 32767, если диапазон требуемых чисел больше, то результат будет с пропусками.)

Для оценки распределения вывожу все 1000000 точек на экран в виде квадрата 1000*1000. Чем больше попаданий ГСЧ в точку, тем ярче ее цвет.
Задал 10 млн повторений ГСЧ, в среднем в каждую точку должно попасть 10 раз.

Распределение вероятности для rnu.Rand_01() от 0 до 1 000 000.

Для RandomInteger()

Для MathRand()

Как видим первые 2 имеют достаточно равномерное распределение, а по MathRand() имеет пропуски.

Пропуски обусловлены разрешением MathRand = 32767. При множителе 1000000 получим пропуски 1000000/32767=30 точек. MathRandomUniform по картинке похожа, видимо те же пропуски в 30 единиц.

Другой вариант, зададим максимальное число 30000. Первые 2 равномерны, как при миллионе.
MathRand выглядит так (с увеличеным куском):

Пропусков (черных точек до позиции 30000) нет. Но часть заметно ярче. Это неравномерность округления 30000/32767. Каждая 11-я точка получает вдвое больше попаданий.

Что-то равномерное можно получить от MathRand при максимуме до 3000... 4000. Вот увеличенный вариант для 3500:


Первые два варианта думаю при приближении максимального числа к 100 миллионам для моей функции (у которой разрешение около 1 миллиарда) и 400 млн. при разрешении 4 млрд. для RND. - тоже начнут неравномерно распределяться за счет округления.

Доработаем:

long RandomLong(long max_vl){return (long)MathFloor((MathRand()+MathRand()*32767.0+ MathRand()*1073741824.0)/35184372088832.0*max_vl);}//случайное long от 0 до  35184372088832

Точность 35184 миллиардов, т.е. при максимальной точке до 3000 миллиардов распределение будет равномерно. Функция стала на 30% медленнее RandomInteger, за счет третьего вызова MathRand().

Для себя решил пользоваться своей функцией, она медленнее на 25% чем RND, но компактнее и понятнее.

Примечание:
Разработчик RND из статьи указал, что

При seed=0 функция меняет начальные значения случайным образом.

При каждом перезапуске, ставлю seed=0, но картинка с распределениями при перезапусках не меняется. Т.е. утверждение неверное. Т.о. надо для случайной инициализации надо seed= устанавливать в случайное число, например seed=GetTickCount();

Для моей функции RandomInteger() при перезапусках видно перераспределение точек. Если установить srand(0); то при перезапусках распределение тоже начинает повторяться. Т.е. для этой функции для случайной инициализации надо либо не вызывать srand, либо тоже с меткой врмени MathSrand(GetTickCount()).

Файл приложил, можете повторить эксперимент.

Файлы: