srand() und rand() auslagern
-
Hi, dieses Programm soll eine Zufallszahl berechnen, wobei der
Benutzer die Grenzen eingeben kann. Es funktioniert, wenn alles
in der main Funktion steht. Wenn ich jedoch einen Teil in eine Klasse
auslagern möchte, kommt die Fehlermeldung:
Verweis auf nicht aufgelöstes externes Symbol ""private: int__thiscall zufallszahl::rand(void)"
?rand@zufallszahl@AAEHXZ...zufallszahl.h:
#ifndef ZUFALLSZAHL_H #define ZUFALLSZAHL_H class zufallszahl { public: zufallszahl(); void definemo(int o); void definemu(int u); int showmo(); int showmu(); int berechne(); private: int mo; int mu; int srand(int x); //Diese Funktion macht Probleme int rand(); //Diese Funktion macht Probleme }; #endif // ZUFALLSZAHL_H
zufallszahl.cpp:
#include "zufallszahl.h" #include <ctime> zufallszahl::zufallszahl() { } void zufallszahl::definemo(int o){ this->mo = o; } void zufallszahl::definemu(int u){ this->mu = u; } int zufallszahl::showmo(){ return this->mo; } int zufallszahl::showmu(){ return this->mu; } int zufallszahl::berechne(){ //Diese Funktion macht Probleme srand(time(NULL)); return rand() % (mo-mu)+mu; }
main.cpp:
#include <iostream> #include "zufallszahl.h" using namespace std; int main() { zufallszahl z; int io, iu; cout<<"Obere Granze: "; cin>>io; cout<<"Untere Grenze: "; cin>>iu; z.definemo(io); z.definemu(iu); cout<<"Grenzen: "<<z.showmo()<<" "<<z.showmu()<<endl; cout<<"Die Zufallszahl ist: "; cout<<z.berechne(); return 0; }
-
srand()
undrand()
sind Funktionen der Standardlibrary und in<cstdlib>
deklariert. Du brauchst sie also nicht in deiner Klasse deklarieren:@theAnfänger77 sagte in srand() und rand() auslagern:
int srand(int x); //Diese Funktion macht Probleme int rand(); //Diese Funktion macht Probleme
Hier solltest Du
std::rand()
aufrufen stattrand()
:@theAnfänger77 sagte in srand() und rand() auslagern:
int zufallszahl::berechne() { //Diese Funktion macht Probleme srand(time(NULL)); return rand() % (mo-mu)+mu; }
... und
std::srand()
, das den Zufallszahlengenerator initialisiert (seeded) nur einmal zu beginn deines Programms. Von mir aus in Konstruktor vonzufallszahl
.Auch solltest du dir ein wenig ausdrucksvollere Variablenamen überlegen.
-
Super!
Vielen Dank für die Antwort!
-
- rand() ist vollkommen veraltet, das wird in C genutzt, aber nicht in C++. Nutze stattdessen die Funktionen des Headers
<random>
. Wenn du doch einmal (in C) rand() benutzen solltest: srand() wird nur einmal im Programm (üblicherweise am Anfang) aufgerufen. - lass den leeren ctor weg, das ist überflüssig und schlechter Stil. Wenn der ctor nichts macht, musst du ihn auch nicht hinschreiben. Wenn du ihn unbedingt im Code haben willst, nutze zumindest
zufallszahl() = default;
. (wenn er leer ist; für<random>
brauchst ihn ggf. um die member zu initialisieren) - Was soll
mo
sein? Was sollmu
sein? Man kann aus dem Kontext erahnen, dass das eine wohl für "oben" und das andere für "unten" stehen soll; eindeutig ist das aber nicht. Gute Namen sind wichtig! Hier wären z.B. max und min aussagekräftiger. - Bei zusammengesetzten Namen gibt es verschiedene Möglichkeiten, das darzustellen. Üblich sind z.B. Unterstriche
define_mo
oder camelCasedefineMo
. Gerade bei komplexeren Namen ist es sonst unnötig schwer, das zu entziffern. - Es ist üblich, eigene Klassennamen mit einem Großbuchstaben zu beginnen (Zufallszahl)
- Versuche, deinen Code einsprachig zu halten - Deutsch oder Englisch. Warum define/show? Der Rest ist doch auch Deutsch. Oder mach alles auf Englisch, vermutlich fast sogar noch besser.
definemx
undshowmx
klingt sehr merkwürdig. Stattdefine
würde ich lieberset_
verwenden undshow
klingt so, als würdest du etwas anzeigen. Stattdessen, gibst du aber etwas zurück, da istget_
besser. Das sind auch die beiden üblichen Namen für solche getter-/setter-Funktionen.- Insgesamt ist die gesamte Aufgabe allerdings noch einmal zu überdenken. Das ganze manuell mit std::mt19937 zu machen ist vermutlich einfacher.
Hier ein Beispiel:
#ifndef RANDOMNUMBER_H #define RANDOMNUMBER_H #include <random> class Random_Number { public: Random_Number(int max, int min) : dist(min, max), mt(std::random_device{}()) { } void set_max(int new_max) { dist.param(decltype(dist.param()){dist.min(), new_max}); } void set_min(int new_min) { dist.param(decltype(dist.param()){new_min, dist.max()}); } int get_max() const { return dist.max(); } int get_min() const { return dist.min(); } int calculate() { return dist(mt); } // besser wäre es wohl, der calculate Funktion max/min zu übergeben; // dann könntest du nämlich direkt `std::uniform_int_distribution<int>{min, max}(mt)` zurückgeben // und setter + dist-member weglassen private: std::uniform_int_distribution<int> dist; std::mt19937 mt; }; #endif // RANDOMNUMBER_H
- rand() ist vollkommen veraltet, das wird in C genutzt, aber nicht in C++. Nutze stattdessen die Funktionen des Headers
-
Vielen Dank für die Antwort!
-
@Unterfliege sagte in srand() und rand() auslagern:
std::mt19937
Es ist aber schon klar, das auch die hier gezeigte C++ Variante ebenso unter vielen Problemen leidet?
- Auf einigen Plattformen ist das gesamte Ergebnis zufällig auf anderen immer gleich.
- MT kann irgendwas um die 2^20000 Sequenzen Erzeugen, diese Implementation erzeugt aber maximal 2^32.
- Ein bestimmter Aufruf (z.B. der erste) von calculate erzeugt evtl. nicht jede Zahl mit der gleichen Wahrscheinlichkeit, auch wenn man das vom Code her erwarten könnte.
- Speicher und Laufzeit ist um ein Vielfaches erhöht.
-
Dieser Beitrag wurde gelöscht!
-
@TGGC Der oben gezeigte Code ist tatsächlich kein wirkliches Musterbeispiel für die Nutzung von
std::mt19937
. Dasstd::uniform_int_distribution
Objekt sollte z.B. eigentlich auch lieber jedes Mal neu konstruiert werden als immer umständlich die Grenzen neu zu setzen.
Man sollte dazu aber auch sagen, dass eine Klasse für solch eine Funktionalität insgesamt ziemlich überflüssig ist. Denstd::mt19937
nutzt man eigentlich einfach an der Stelle, an der man ihn braucht bzw. reicht auch eine einfache Funktion.Zu deinem Punkten:
- Ja, das liegt daran, dass einige (wenige) Implementationen mit
std::random_device
immer genau die gleiche Sequenz zurückgeben und noch nicht einmal diesen Wert jedes Mal mit einem neuen Seed füttern. Das gilt meines Wissens aber nur für den MinGW (GCC for Windows). Ansonsten iststd::random_device
eigentlich die beste Methode, um einen Zufallswert zu erhalten. Für etwas besseres müsste ansonsten boost verwendet werden. - Was meinst du mit "diese Implementierung"? Ich vermute, dass du auf irgendeine Implementierung der Standard-Lib anspielst - kann dir dabei aber nicht sagen, wie gut das implementiert ist. Die Periodenlänge dürfte sich aber eigentlich nicht verändern. Oder meinst du, dass der
std::mt19937
schlecht gesäht wurde? Das stimmt - und das schränkt tatsächlich den Algorithmus stark ein. Dazu hat der Schwertfisch auch einmal auf folgenden Link hingewiesen: http://www.gockelhut.com/cpp-pirate/random-seed.html. Ja, hätte man vermutlich in dem Beispiel besser machen sollen, das war allerdings zugegebenermaßen bevor ich den Artikel gelesen habe. - Du meinst, dass man
std::uniform_int_distribution
jedes Mal neu konstrukieren sollte, vermute ich mal. Ja, sollte man wohl lieber, hast du recht. - Mit was vergleichst du gerade? Warum ist was höher?
- Ja, das liegt daran, dass einige (wenige) Implementationen mit
-
@Unterfliege ad 2: Das, was ich andernorts auch schon gesagt habe. Den mt mit nur 32 bits seeden ist. ... doof.
-
@Swordfish Aber das dürfte eigentlich nicht die Periodenlänge des Algorithmuses ändern, oder irre ich mich da? Und ich weiß ja noch nicht einmal, ob @TGGC das meinte.
Deinen Artikel habe ich deswegen aber ja auch schon verlinkt.
-
@Unterfliege Die Periodenlänge nicht, aber die Anzahl der möglichen Perioden. Ja, meint er.
-
@TGGC sagte in srand() und rand() auslagern:
Speicher und Laufzeit ist um ein Vielfaches erhöht.
Speicher ja. Laufzeit von
mt19937
ist aber überraschenderweise wirklich sehr gut. Davon abgesehen hab ich an deiner Kritik nichts zu kritisieren xD
-
@Swordfish also unter einer Periode verstehe ich die Anzahl an Zufällen, die der Algorithmus generieren kann, bevor er sich wiederholt. Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern (ich habe mich mit dem MT ehrlich gesagt nie direkt auseinandergesetzt). Ich will allerdings auch nicht ausschließen, dass ich diesbezüglich falsch liege.
@hustbaer was nutzt du denn dann eigentlich als Alternative? Separate lib oder seedest du einfach nur den
mt19937
besser (wie auch in dem Artikel, den ich oben verlinkt habe, gezeigt wird)? Oder akzeptierst du einfach die Nachteile?
-
@Unterfliege sagte in srand() und rand() auslagern:
also unter einer Periode verstehe ich die Anzahl an Zufällen, die der Algorithmus generieren kann, bevor er sich wiederholt.
Ja.
@Unterfliege sagte in srand() und rand() auslagern:
Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern
Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.
-
@Swordfish sagte in srand() und rand() auslagern:
Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.
Okay, dann habe ich das missverstanden. Ich ging davon aus, dass
std::mt19937
die restlichen Bits von seinem state einfach mit 0 intialisiert/uninitialisiert nutzt.
-
@Swordfish sagte in srand() und rand() auslagern:
Dabei ist es dann wohl eher so, dass die Aufrufe nach 2^32 Aufrufen schlechten/gar keinen Zufall liefern
Wenn du mit 32 bit seedest hast du danach wieder die selben Sequenzen.
Auch wenn man nur mit 32 bit seeded, ist die Periode dennoch deutlich länger. Du hast eben nur 2^32 initial states. Die Periodenlänge ist, Achtung, man könnte es am Namen erraten, (Quelle). Diese ultralange Periode ist neben der guten Verteilung der Zahlen ja gerade toll an MT.
-
@wob Du hast mich falsch verstanden. Wenn man mit einem wert 0 <= 2^32-1 seeded hat man eine Sequenz der länge $Periodenlänge für 0, eine Sequenz der länge $Periodenlänge für 1, eine Sequenz der länge $Periodenlänge für 2, ...
-
@Unterfliege sagte in srand() und rand() auslagern:
@hustbaer was nutzt du denn dann eigentlich als Alternative? Separate lib oder seedest du einfach nur den mt19937 besser (wie auch in dem Artikel, den ich oben verlinkt habe, gezeigt wird)? Oder akzeptierst du einfach die Nachteile?
Das letzte mal wo ich gute Zufallszahlen* brauchte hab ich
mt19937
genommen und überCryptGenRandom
geseedet. Auf POSIX Systemen kann man/dev/random
oder/dev/urandom
verwenden. Heutzutage würde ich aber eherstd::random_device
verwenden.*: Der
mt19937
ist lange nicht so gut wie viele meinen. Wenn du nen guten, schnellen, einfachen Generator willst, schau dir die Xoroshiro Familie an. Bzw. google einfach selbst mal nach dem Thema.
-
@hustbaer sagte in srand() und rand() auslagern:
Der mt19937 ist lange nicht so gut wie viele meinen.
Naja, ich würde mal sagen, dass der
am besten General-Purpose-Zwecke erfüllendebeste Pseudo Random Number Generator ist, den es fertig im C++-Standard gibt. Daher empfehle ich diesen im Regelfall für alles mögliche. Wenn man mehr braucht: gerne, spricht nichts dagegen!Heutzutage würde ich aber eher std::random_device verwenden.
Ja? Als ich das hier mal vorgeschlagen hatte für ein paar Zufallszahlen, warst du vor noch nicht mal einem Jahr noch anderer Meinung:
https://www.c-plusplus.net/forum/topic/347207/array-wörter-zufällig-aufrufen/12
-
@wob
random_device()
zum seeden, nicht als RNG.