Merkmale der Sprache mql5, Feinheiten und Techniken - Seite 136

 
Nikolai Semko:
Sie sind also bereit, einen 100-Dollar-Schein anzuzünden, um einen unter das Bett gerollten Groschen zu finden?

Kann, muss aber keine Münze sein

   srand(GetTickCount());
   uint v10 = 0, v19900 = 0;
   for (uint i = 0;  i < UINT_MAX;  ++ i) {
      int cur = rand();
      if (cur % 20000  == 10)
         ++ v10;
      else if (cur % 20000  == 19900)
         ++ v19900;   
   }
   Alert("10 probability = ", (double)v10/UINT_MAX*100, "%;  1990 probability = ", (double)v19900/UINT_MAX*100, "%");
   // Alert: 10 probability = 0.006103515626421085%;  19900 probability = 0.003051757813210543%
        

Die Wahrscheinlichkeit für eine Zehn ist doppelt so hoch. Und was am wichtigsten ist: Der Anspruch auf die Kosteneffizienz von get_rand() wird einem aus der Hand gerissen. Warum sollte man also Zufallszahlen durch die Hintertür mit einer verschobenen Wahrscheinlichkeit erhalten (während man eine Gleichverteilung erwartet), wenn man eine Normalverteilung haben kann? Du sparst keine 100-Dollar-Note, sondern Streichhölzer.

 
Vict:

Kann, muss aber keine Münze sein

Die Wahrscheinlichkeit für eine Zehn ist doppelt so hoch. Und was am wichtigsten ist: Der Anspruch auf die Kosteneffizienz von get_rand() wird einem aus der Hand gerissen. Warum sollte man also Zufallszahlen durch die Hintertür mit einer verschobenen Wahrscheinlichkeit erhalten (während man eine Gleichverteilung erwartet), wenn man eine Normalverteilung haben kann? Du sparst keine 100-Dollar-Note, sondern Streichhölzer.

Ja, ich habe mich geirrt, was die große Langsamkeit Ihrer Funktion angeht. Ich habe den Algorithmus missverstanden. Entschuldigung.
Dennoch ist mein Algorithmus der schnellste von allen vorgeschlagenen, obwohl er universeller und nicht auf 32767 beschränkt ist, wie der Ihre.
Code, um es zu beweisen.

Dieses Skript erzeugt nach dem Zufallsprinzip eine Reihe von Punkten mit zufälliger Farbe und zufälligen Koordinaten. Die Größe des Arrays entspricht der Anzahl der Pixel auf dem Diagrammdiagramm. Es wird 5 Mal wiederholt

  1. mit der regulären Funktion rand()
  2. mit Ihrer Funktion get_rand()
  3. mit meiner Funktion ulong randUlong()
  4. mit meiner Funktion uint randUint()
  5. mit der ulong-Funktion RandomLong()von@AlexeyNavoykov
2019.06.0903:42:25.958 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 9894 микросекунд.  Всего сгенерировано 5203975 случайных чисел rand()
2019.06.0903:42:28.010 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 24899 микросекунд. Всего сгенерировано 5203975 случайных чисел get_rand()
2019.06.0903:42:30.057 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 22172 микросекунд. Всего сгенерировано 5203975 случайных чисел randUlong()
2019.06.0903:42:32.098 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 16013 микросекунд. Всего сгенерировано 5203975 случайных чисел randUint()
2019.06.0903:42:34.145 TestSpeedRand (EURGBP,H4)       Время формирования случайных массивов = 25524 микросекунд. Всего сгенерировано 5203975 случайных чисел RandomLong()

Ich habe die Zahlen so ausgewählt, dass sie das Wesen des Problems zeigen, wenn wir rand()%20000 anwenden


wie es sein sollte:


//+------------------------------------------------------------------+
uint get_rand(uint max)
  {
   staticbool f=false;
   if(!f) 
     {
      f=true;
      srand(GetTickCount());
     }
   uint limit=(max+1) *((32767+1)/(max+1));
   uint val;
   while((val=rand())>=limit);
   return val % (max+1);
  }
//+------------------------------------------------------------------+
ulong randUlong(ulong max=ULONG_MAX){return(((ulong)rand()<<60)|((ulong)rand()<<45)|((ulong)rand()<<30)|((ulong)rand()<<15)|(ulong)rand())%max;}
//+------------------------------------------------------------------+
uint randUint(uint max=UINT_MAX) {return(((uint)rand()<<30)|((uint)rand()<<15)|(uint)rand())%max;}
//+------------------------------------------------------------------+
ulong RandomLong(ulong range)
  {
#define _MAXRND(rang,rnd_range) ((rnd_range) -((rnd_range)-rang)%rang-1)
#define _RND ulong(rand())
   ulong rnd,max,const bit=1;
   if(range <= bit<<15) { if(!range) return0;  max=_MAXRND(range, 1<<15);  while((rnd=_RND) > max);  return rnd%range; }
   if(range <= bit<<30) { max=_MAXRND(range, bit<<30);  while((rnd=(_RND | _RND<<15)) > max);  return rnd%range; }
   if(range <= bit<<45) { max=_MAXRND(range, bit<<45);  while((rnd=(_RND | _RND<<15 | _RND<<30)) > max);  return rnd%range;  }
   if(range <= bit<<60) { max=_MAXRND(range, bit<<60);  while((rnd=(_RND | _RND<<15 | _RND<<30 | _RND<<45)) > max);  return rnd%range; }
   else  { max=_MAXRND(range,bit<<64);  while((rnd=(_RND|_RND<<15|_RND<<30|_RND<<45|_RND<<60))>max);  return rnd%range; }
#undef _RND
#undef _MAXRND
  }
//+------------------------------------------------------------------+
Aber in 99,9 % der Fälle funktioniert auch diese Funktion:
uint randUint(uint max) {return(((uint)rand()<<15)|(uint)rand())%max;}
wird es noch schneller funktionieren.
Diese Funktion generiert eine Zufallszahl von 0 bis 1073741824, die sogar größer ist als die Anzahl der Ticks für jedes Instrument in der gesamten Historie. Die "Ungerechtigkeit" einer solchen Funktion wäre bei 99,9 % der Aufgaben mikroskopisch klein.
Dateien:
 
Nikolai Semko:

Dennoch erweist sich mein Algorithmus als der schnellste aller vorgeschlagenen Algorithmen, obwohl er universeller und nicht wie der Ihre auf 32767 beschränkt ist.
Der Code als Beweis.

Danke für die Arbeit, wirklich interessante Ergebnisse. Es stellt sich heraus, dass die Funktion rand() so schnell ist, dass sie schneller arbeitet als arithmetische Operationen.

 
Alexey Navoykov:

Vielen Dank für die Mühe, wirklich interessante Ergebnisse. Es stellt sich heraus, dass rand() so schnell ist, dass es schneller ist als arithmetische Operationen.

Nein, das ist es nicht. Etwa eine Nanosekunde, genau wie beim Ziehen der Quadratwurzel aus einer doppelten Zahl. Die +-*/-Operationen werden in Bruchteilen einer Nanosekunde durchgeführt.
Aber genau wie die Quadratwurzel wird rand() in modernen Prozessoren auf der Hardwareebene ausgeführt, nicht programmatisch.

 
Nikolai Semko:

nein, nicht schneller. Etwa eine Nanosekunde, genau wie beim Ziehen der Quadratwurzel aus einer doppelten Zahl. Die +-*/-Operationen werden in Bruchteilen einer Nanosekunde durchgeführt.
Aber genau wie die Quadratwurzel wird rand() in modernen Prozessoren auf der Hardwareebene ausgeführt, nicht programmatisch.

Ihre Version unterscheidet sich von meiner dadurch, dass rand() immer 5 Mal aufgerufen wird, während meine Version im Durchschnitt 1,64 Mal bei 20 000 und 1 Mal bei 256 aufruft. Insgesamt wird rand() in Ihrem Code 25 Mal pro Iteration aufgerufen, während meine Version 1,64*2+3 = 5,3 Mal aufgerufen wird. Natürlich ist die Situation seltsam, wir müssen herausfinden, was genau der Grund dafür ist. Sie haben eine Menge von bitweisen Operationen, die dort zusätzlich ausgeführt werden...

 
Nikolai Semko:

1. Nun, wir wissen, dass in Ihren Funktionen das Problem nicht gelöst, sondern nur verschleiert wird: Ich werde nicht abnehmen, ich werde nur meinen Gürtel enger schnallen.

2. In unseren und Alexeys Varianten ist dies der ungünstigste Fall, während in vielen anderen Situationen die Geschwindigkeit fast auf dem Niveau von rand() liegt, während Sie konstante Zeit haben.

Haben Sie sich jemals gefragt, warum rand() Zahlen in einem so engen Bereich erzeugt? Da es sich um einen Pseudo-Zufallsgenerator handelt, ist er periodisch, d. h., er erzeugt einen Haufen Zufallszahlen an Stellen, an denen er nicht gebraucht wird, und verwirft sie anschließend wieder, wodurch sich seine Qualität verschlechtert (er wird sich früher wiederholen).

4. Manche Leute werten Zufallsdaten auf eine kompliziertere Weise aus. Wenn ich zum Beispiel etwas aus dem Netz ziehe, kauft es vielleicht sogar jemand. Warum sollte ich hart erarbeitete Daten verschwenden, um sie dann stumpfsinnig zu verwerfen (das Erzeugen eines langen, korrekten Algorithmus ist schließlich nicht unsere Art)?

 
Alexey Navoykov:

Ihre Version unterscheidet sich von meiner dadurch, dass sie immer 5 mal rand() aufruft, während meine Version im Durchschnitt 1,64 mal bei einem Bereich von 20 000 und 1 mal bei einem Bereich von 256 aufruft. Insgesamt wird Ihre rand() bei jeder Iteration 25 mal aufgerufen, während meine Version 1,64*2+3 = 5,3 mal aufruft. Natürlich ist diese Situation seltsam, wir müssen herausfinden, was genau der Grund dafür ist, denn Sie haben eine Menge bitweiser Operationen, die dort zusätzlich ausgeführt werden...

Bits sind die billigsten Operationen. Fast kostenlos.

Aber im Großen und Ganzen stimme ich zu. Ich verstehe auch nicht, warum... Vielleicht liegt es an den Wundern der Optimierung. Obwohl das, was dort optimiert werden kann...

 
Nikolai Semko:

Bits sind die billigsten Operationen. Fast kostenlos.

Aber im Großen und Ganzen stimme ich zu. Ich verstehe auch nicht, warum... Vielleicht liegt es an den Wundern der Optimierung. Auch wenn es etwas zu optimieren gibt...

Es scheint, dass es sich nicht um arithmetische Operationen handelt, denn es gibt keine, alle Werte werden in der Kompilierungsphase berechnet. Der Grund ist das Vorhandensein einer Schleife mit einer unbekannten Anzahl von Iterationen (obwohl diese Iterationen im Durchschnitt weniger als zwei sind). Ihr Code ist also aufgrund der bekannten Anzahl von rand()-Aufrufen irgendwie optimiert
 
Vict:

1. Nun, wir wissen, dass in Ihren Funktionen das Problem nicht gelöst, sondern nur verschleiert wird: Ich werde nicht abnehmen, ich werde nur meinen Gürtel enger schnallen.

2. In unserer und Alexeys Variante ist das der schlimmste Fall, während in vielen anderen Situationen die Geschwindigkeit fast auf dem Niveau von rand() liegt, während Sie konstante Zeit haben.

Haben Sie sich jemals gefragt, warum rand() Zahlen in einem so engen Bereich erzeugt? Da es sich um einen Pseudo-Zufallsgenerator handelt, ist er periodisch, d. h., er erzeugt einen Haufen Zufallszahlen an Stellen, an denen er nicht gebraucht wird, und verwirft sie anschließend wieder, wodurch sich seine Qualität verschlechtert (er wird sich früher wiederholen).

4. Manche Leute werten Zufallsdaten auf eine kompliziertere Weise aus. Wenn ich zum Beispiel etwas aus dem Netz ziehe, kauft es vielleicht sogar jemand. Warum sollte ich also hart erarbeitete Daten verschwenden, um sie dann dummerweise zu verwerfen (das Erzeugen eines langen, richtigen Algorithmus ist schließlich nicht unsere Art)?

Nun, das ist nerdy.
Um die Situation zu reproduzieren, in der sich dieses Problem um mindestens 0,1 % bemerkbar macht, müssen Sie mit Bereichen oberhalb der folgenden Werte arbeiten:

  • (ULONG_MAX)/1000= 18446744073709551 für die Funktion randUlong()
  • (UINT_MAX)/1000= 4294967 für die Funktion randUint()

Haben Sie jemals solche Bereiche verwendet? Warum haben Sie dann diese Prüfungen und Schleifen eingebaut?
Das Beste ist der Feind des Guten.

Ich persönlich beschränke meineZufallsgenerierungsbereiche in der Praxis auf 2000, höchstens 4000. rand() funktioniert für diesen Zweck recht gut.
Fügen Sie eine solche Option in meinen Code ein:

ulong t=GetMicrosecondCount();
   for(int i=0;i<size;i++)
     {
      X[i]=ushort(rand()%W.Width);
      Y[i]=ushort(rand()%W.Width);
      clr[i]=XRGB(rand()%256,rand()%256,rand()%256);
     }
   t=GetMicrosecondCount()-t;
   Print("Время формирования случайных массивов = "+string(t)+" микросекунд. Всего сгенерировано "+string(size*5)+" случайных чисел rand()%W.Width");
   Canvas.Erase();
   for(int i=0;i<size;i++) Canvas.PixelSet(X[i],Y[i],clr[i]);
   Canvas.Update();
   Sleep(2000);

So werden Sie die "Ungerechtigkeit" der rand()-Funktion nicht bemerken (wie es bei rand()%20000 der Fall war) und die Punkte werden visuell gleichmäßig angeordnet, so dass es die schnellste und effizienteste Funktion ist.

Nicht umsonst haben die Prozessorentwickler rand() auf 2^15=32768 begrenzt. Das sind keine dummen Menschen. Das deckt 99 % der praktischen Probleme ab.
Und für Liebhaber "extremer" Ideen gibt es mehr als genug Möglichkeiten:

ulong randUlong(ulong max=ULONG_MAX){return(((ulong)rand()<<60)|((ulong)rand()<<45)|((ulong)rand()<<30)|((ulong)rand()<<15)|(ulong)rand())%max;}
Dateien:
 
Nikolai Semko:

Persönlich sind meineZufallszahlengenerierungsbereiche in der Praxis auf 2000, maximal 4000 begrenzt. Hierfür eignet sich rand() recht gut.
Fügen Sie eine solche Variante in meinen Code ein:

und Sie werden die "Unfairness" der rand()-Funktion nicht bemerken (wie bei rand()%20000) und die Punkte werden visuell einheitlich sein, so dass es gut funktioniert und am schnellsten ist.

Sie können es gerne verwenden, ich habe nichts dagegen. Dies gilt umso mehr, wenn die rechte Seite von % ein Vielfaches von RAND_MAX+1 (256 oder 1024) ist.

Nicht umsonst haben die Prozessorentwickler rand() auf 2^15=32768 begrenzt. Das sind keine dummen Menschen. Es deckt 99 % der praktischen Aufgaben ab.
Und es gibt mehr als genug Varianten für diejenigen, die gerne "ausgefallene" Ideen haben:

Was haben Prozessorentwickler damit zu tun? Der Generator ist in Software implementiert. Die einzige Voraussetzung ist RAND_MAX >= 32767 und eine Periode von mindestens 2^32. Der µl hat also einen sehr spärlichen Oszillator bei "Minimalwerten".

Und die ganz Weitsichtigen werden sich einen fairen rand() machen (wenn es keine Vielheit gibt), das wird sogar in Fachbüchern empfohlen.