Как MathRand() генерирует значения? - страница 3

 

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

Задал 10 млн повторений ГСЧ, в среднем в каждую точку должно попасть 10 раз.

Вот результаты:
RND:


Мой вариант RandomInteger:


На MathRand():


На MathRandomUniform():


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

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


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

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


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

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


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

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

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

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

Файлы:
 

Учтите, что xor128 проходит специальные тесты на случайность, а со стандартным rand может быть такое

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Машинное обучение в трейдинге: теория, практика, торговля и не только

Rorschach, 2020.04.19 22:18

Я рукожоп, ГСЧ сломал(((

#include <Canvas\Canvas.mqh>
void OnStart()
  {CCanvas C;
   int h=1024;
   int w=2048;
   C.CreateBitmapLabel("11",100,100,w,h);
   for(int y=0;y<h;y++)
     {//srand(GetMicrosecondCount());
      for(int x=0;x<w;x++)
        {uchar c=0;
         for(int k=0;k<16;k++)
           {c=uchar(255.*rand()/32767.);
           }
         C.PixelSet(x,y,ARGB(255,c,c,c));
        }
     }
   C.Update();

1 способ: w должно быть степенью 2, k кратно 4

2 способ: раскоментировать srand

2 способ должен сработать так же на Вихре Мерсена

 
Rorschach:

Учтите, что xor128 проходит специальные тесты на случайность, а со стандартным rand может быть такое



Вы отображаете каждый 16-й вызов rand. Какая то закономерность из этой периодичности проявляется.
У меня используется все подряд вызовы по 2 в каждом. Визуально, по моим картинкам не вижу разницы с xor128 для 10 млн повторений.
 
Rorschach:

Учтите, что xor128 проходит специальные тесты на случайность, а со стандартным rand может быть такое


Попробовал в вашу функцию подставить свою и xor128

#include <Canvas\Canvas.mqh>
void OnStart()
  {CCanvas C;
  RND rn;   rn.SRand(0);
   int h=1024;
   int w=2048;
   C.CreateBitmapLabel("11",100,100,w,h);
   for(int y=0;y<h;y++)
     {//srand(GetMicrosecondCount());
     rn.SRand(GetMicrosecondCount());
      for(int x=0;x<w;x++)
        {uchar c=0;
         for(int k=0;k<16;k++)
           //{c=uchar(255.*rand()/32767.);}
           //{c=uchar(255.*RandomInteger(1073741824)/1073741824.0); }
           {c=(uchar)(255.0*rn.Rand_01()); }
         C.PixelSet(x,y,ARGB(255,c,c,c));
        }
     }
   C.Update();
   }
  
   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 мс. Это почти как RandomInteger()

#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 RND{
protected:
   uint      x,y,z,w,xx,t;
public:
      RND(void){inidat;};
    ~RND(void){};
   uint      RandMaxUI(uint max_v)   {xor128;return((uint)MathFloor((double)w/UINT_MAX*max_v));};//равномерное распределение на отрезке [0,max_v]. uint
   int       RandMaxI(int max_v)     {xor128;return((int) MathFloor((double)w/UINT_MAX*max_v));};//равномерное распределение на отрезке [0,max_v]. int
   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;}
   };
};


C RandomInteger() какие то разводы тоже есть.
С xor128 получше, но есть какая-то полоса:

А вообще не уверен, что при обычном использовании, (а не 1 из 16) эти разводы из RandomInteger() как то повлияют... Да и не понятно, что они означают...

Но пожалуй xor128 все же надежнее использовать.

 
Rorschach:

Учтите, что xor128 проходит специальные тесты на случайность, а со стандартным rand может быть такое


А у вас есть код Мерсена? У меня где-то был на OpenCL, руки не дошли перенести на MQL5. Интересно было бы сравнить.

2 способ должен сработать так же на Вихре Мерсена


 
Rashid Umarov:

А у вас есть код Мерсена? У меня где-то был на OpenCL, руки не дошли перенести на MQL5. Интересно было бы сравнить.

Здесь, сам не пользовался.

 
elibrarius:

Вы отображаете каждый 16-й вызов rand. Какая то закономерность из этой периодичности проявляется.
У меня используется все подряд вызовы по 2 в каждом. Визуально, по моим картинкам не вижу разницы с xor128 для 10 млн повторений.

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

 
Rorschach:

Здесь, сам не пользовался.

Спасибо!  Код оказался больше, чем на OpenCL. Как нибудь постараюсь разобраться.

 

дружный попадос в тер.вер., магию целых чисел, округления, модульную арифметику и фокусы double :-)

если f(x) :: integer равномерно распределённое (0;N] то это отнюдь не значит что g(x)=N/(double)f(x) будет равномерно распределено на (0;1] и к тому-же пройдёт все тесты. 

примерно то же самое про g(x) = f(x) mod M , за редким исключением N mod M = 0. 

PS/ кстати и g(x)=f(x)/(double)N тоже не будет равномерной. Там будут фокусы деления. То есть на графиках будет проявляться муар.

PPS/ для визуального "теста" на рандомность,кроме тупого квадрата по модулю, применяются вариации "улитки улама"

 
Rorschach:

Здесь, сам не пользовался.

Замерил производительность CRandomMersenne и CRandomMother из этого кода. Они в 3 раза медленнее xor128. Это не очень радует. Но по вашему коду, где вы сломали ГСЧ, они выглядят лучше, чем xor128 (без вертикальной полоски).