Ich habe festgestellt, dass rand()
Bibliotheksfunktionen, wenn sie nur einmal innerhalb einer Schleife aufgerufen werden, fast immer positive Zahlen erzeugen.
for (i = 0; i < 100; i++) {
printf("%d\n", rand());
}
Wenn ich jedoch zwei rand()
Anrufe hinzufüge , haben die generierten Nummern jetzt mehr negative Nummern.
for (i = 0; i < 100; i++) {
printf("%d = %d\n", rand(), (rand() + rand()));
}
Kann jemand erklären, warum ich im zweiten Fall negative Zahlen sehe?
PS: Ich initialisiere den Startwert vor der Schleife als srand(time(NULL))
.
rand()
kann nicht negativ sein ...RAND_MAX
für Ihren Compiler? Sie können es normalerweise in findenstdlib.h
. (Witzig: Überprüfungman 3 rand
, es trägt die einzeilige Beschreibung "Generator für schlechte Zufallszahlen".)abs(rand()+rand())
. Ich hätte lieber eine positive als eine negative UB! ;)Antworten:
rand()
ist definiert, um eine Ganzzahl zwischen0
und zurückzugebenRAND_MAX
.könnte überlaufen. Was Sie beobachten, ist wahrscheinlich ein Ergebnis eines undefinierten Verhaltens, das durch einen Ganzzahlüberlauf verursacht wird.
quelle
a + b > a
sie das wissen, wenn sie das wissenb > 0
. Sie können auch davon ausgehen, dass bei einer später ausgeführten Anweisung dera + 5
aktuelle Wert niedriger istINT_MAX - 5
. Selbst auf dem 2er-Komplement-Prozessor / Interpreter ohne Traps verhält sich das Programm möglicherweise nicht so, als obint
s das 2er-Komplement ohne Traps wäre.Das Problem ist die Hinzufügung.
rand()
gibt einenint
Wert von zurück0...RAND_MAX
. Wenn Sie also zwei davon hinzufügen, werden Sie aufstehenRAND_MAX * 2
. Wenn dies überschritten wirdINT_MAX
, überschreitet das Ergebnis der Addition den gültigen Bereich, den einint
halten kann. Ein Überlauf von vorzeichenbehafteten Werten ist ein undefiniertes Verhalten und kann dazu führen, dass Ihre Tastatur in Fremdsprachen mit Ihnen spricht.Da das Hinzufügen von zwei zufälligen Ergebnissen hier keinen Vorteil bringt, besteht die einfache Idee darin, dies einfach nicht zu tun. Alternativ können Sie jedes Ergebnis
unsigned int
vor der Addition umwandeln, wenn dies die Summe enthalten kann. Oder verwenden Sie einen größeren Typ. Beachten Sie, dasslong
nicht notwendigerweise breiter ist alsint
das gleiche gilt ,long long
wennint
mindestens 64 Bit!Fazit: Vermeiden Sie einfach den Zusatz. Es bietet keine "Zufälligkeit" mehr. Wenn Sie mehr Bits benötigen, können Sie die Werte verketten
sum = a + b * (RAND_MAX + 1)
, dies erfordert jedoch wahrscheinlich auch einen größeren Datentyp alsint
.Als Ihr angegebener Grund ist es, ein Null-Ergebnis zu vermeiden: Dies kann nicht vermieden werden, indem die Ergebnisse von zwei
rand()
Aufrufen addiert werden , da beide Null sein können. Stattdessen können Sie einfach inkrementieren. WennRAND_MAX == INT_MAX
dies nicht möglich istint
. Allerdings(unsigned int)rand() + 1
tut sehr, sehr wahrscheinlich. Wahrscheinlich (nicht definitiv), weil es erforderlich istUINT_MAX > INT_MAX
, was für alle mir bekannten Implementierungen gilt (die einige eingebettete Architekturen, DSPs und alle Desktop-, Mobil- und Serverplattformen der letzten 30 Jahre abdecken).Warnung:
Obwohl bereits hier in den Kommentaren bestreut, beachten Sie bitte , dass das Hinzufügen von zwei Zufallswerten nicht nicht erhält eine gleichmäßige Verteilung, sondern eine Dreiecksverteilung mit zwei Würfel wie walzen zu bekommen
12
(zwei Würfel) beiden Würfel zeigen müssen6
. denn11
es gibt bereits zwei mögliche Varianten:6 + 5
oder5 + 6
usw.Daher ist der Zusatz auch in dieser Hinsicht schlecht.
Beachten Sie auch, dass die
rand()
generierten Ergebnisse nicht unabhängig voneinander sind, da sie von einem Pseudozufallszahlengenerator generiert werden . Beachten Sie auch, dass der Standard nicht die Qualität oder gleichmäßige Verteilung der berechneten Werte festlegt.quelle
UINT_MAX > INT_MAX != false
der Standard dies garantiert. (Klingt wahrscheinlich, ist sich aber bei Bedarf nicht sicher). In diesem Fall können Sie nur ein einzelnes Ergebnis umwandeln und inkrementieren (in dieser Reihenfolge!).Dies ist eine Antwort auf eine Klarstellung der Frage, die im Kommentar zu dieser Antwort gestellt wurde.
Das Problem bestand darin, 0 zu vermeiden. Bei der vorgeschlagenen Lösung gibt es (mindestens) zwei Probleme. Eine ist, wie die anderen Antworten zeigen,
rand()+rand()
die undefiniertes Verhalten hervorrufen kann. Der beste Rat ist, niemals undefiniertes Verhalten aufzurufen. Ein weiteres Problem ist, dass es keine Garantie gibt, dassrand()
nicht zweimal hintereinander 0 erzeugt wird.Das Folgende lehnt Null ab, vermeidet undefiniertes Verhalten und ist in den allermeisten Fällen schneller als zwei Aufrufe an
rand()
:quelle
rand() + 1
?rand()
Produzieren Sie grundsätzlich Zahlen zwischen0
undRAND_MAX
und2 RAND_MAX > INT_MAX
in Ihrem Fall.Sie können mit dem Maximalwert Ihres Datentyps modulieren, um einen Überlauf zu verhindern. Dies stört natürlich die Verteilung der Zufallszahlen,
rand
ist jedoch nur ein Weg, um schnelle Zufallszahlen zu erhalten.quelle
Möglicherweise können Sie einen kniffligen Ansatz ausprobieren, indem Sie sicherstellen, dass der durch die Summe von 2 rand () zurückgegebene Wert niemals den Wert von RAND_MAX überschreitet. Ein möglicher Ansatz könnte sum = rand () / 2 + rand () / 2 sein; Dies würde sicherstellen, dass für einen 16-Bit-Compiler mit einem RAND_MAX-Wert von 32767, selbst wenn beide Rand 32767 zurückgeben, selbst dann (32767/2 = 16383) 16383 + 16383 = 32766 keine negative Summe resultiert.
quelle
rand()
nicht beide Null ergeben. Daher ist der Wunsch, Null zu vermeiden, kein guter Grund, zwei Werte hinzuzufügen. Andererseits wäre der Wunsch nach einer ungleichmäßigen Verteilung ein guter Grund, zwei zufällige Werte hinzuzufügen, wenn einer sicherstellt, dass kein Überlauf auftritt.Eine einfache Lösung (okay, nennen Sie es einen "Hack"), die niemals ein Null-Ergebnis erzeugt und niemals überläuft, ist:
Dies begrenzt Ihren Maximalwert, aber wenn Sie sich nicht darum kümmern, sollte dies für Sie gut funktionieren.
quelle
rand()
immer einen nicht negativen Wert zurück). Allerdings würde ich die Optimierung hier dem Compiler überlassen.rand
dass er nicht negativ ist, ist die Verschiebung effizienter als die Division durch eine vorzeichenbehaftete Ganzzahl 2. Die Division durch2u
könnte funktionieren, aber wenn dies der Fallx
ist,int
kann dies zu Warnungen vor impliziter Konvertierung von vorzeichenlos führen zu unterschreiben./ 2
sowieso eine Verschiebung verwenden wird (ich habe dies sogar für so etwas gesehen-O0
, dh ohne ausdrücklich angeforderte Optimierungen). Dies ist möglicherweise die trivialste und etablierteste Optimierung von C-Code. Punkt ist, dass die Division durch den Standard für den gesamten ganzzahligen Bereich gut definiert ist, nicht nur für nicht negative Werte. Nochmals: Überlassen Sie die Optimierung dem Compiler, schreiben Sie zunächst den richtigen und klaren Code. Dies ist für Anfänger noch wichtiger.rand()
durch eins nach rechts verschoben oder durch geteilt wird,2u
als wenn er durch 2 geteilt wird, selbst wenn er verwendet wird-O3
. Man könnte vernünftigerweise sagen, dass eine solche Optimierung wahrscheinlich keine Rolle spielt, aber wenn man sagt, dass "solche Optimierungen dem Compiler überlassen", würde dies bedeuten, dass Compiler sie wahrscheinlich ausführen würden. Kennen Sie irgendwelche Compiler , dass eigentlich will?Versuchen Sie Folgendes, um 0 zu vermeiden:
Sie müssen einschließen
limits.h
.quelle
rand()
0 ergibt.1
und2
(alle angenommenRAND_MAX == INT_MAX
) verdoppeln . Ich habe das vergessen- 1
.-1
hier dient keinem Wert.rand()%INT_MAX+1;
würde immer noch nur Werte im Bereich [1 ... INT_MAX] erzeugen.Während das, was alle anderen über den wahrscheinlichen Überlauf gesagt haben, sehr wohl die Ursache für das Negative sein kann, selbst wenn Sie vorzeichenlose Ganzzahlen verwenden. Das eigentliche Problem besteht darin, die Zeit- / Datumsfunktionalität als Startwert zu verwenden. Wenn Sie sich mit dieser Funktionalität wirklich vertraut gemacht haben, wissen Sie genau, warum ich das sage. Was es wirklich tut, ist eine Entfernung (verstrichene Zeit) seit einem bestimmten Datum / einer bestimmten Uhrzeit anzugeben. Die Verwendung der Datums- / Zeitfunktion als Startwert für einen Rand () ist zwar eine weit verbreitete Praxis, aber nicht die beste Option. Sie sollten nach besseren Alternativen suchen, da es viele Theorien zu diesem Thema gibt und ich unmöglich auf alle eingehen könnte. Sie fügen dieser Gleichung die Möglichkeit eines Überlaufs hinzu, und dieser Ansatz war von Anfang an zum Scheitern verurteilt.
Diejenigen, die rand () + 1 gepostet haben, verwenden die am häufigsten verwendete Lösung, um sicherzustellen, dass sie keine negative Zahl erhalten. Aber dieser Ansatz ist auch nicht der beste Weg.
Das Beste, was Sie tun können, ist, sich die zusätzliche Zeit zu nehmen, um die richtige Ausnahmebehandlung zu schreiben und zu verwenden, und die rand () - Nummer nur dann zu addieren, wenn und / oder wenn Sie am Ende ein Ergebnis von Null erhalten. Und um mit negativen Zahlen richtig umzugehen. Die rand () - Funktionalität ist nicht perfekt und muss daher in Verbindung mit der Ausnahmebehandlung verwendet werden, um sicherzustellen, dass Sie das gewünschte Ergebnis erzielen.
Es lohnt sich, sich die zusätzliche Zeit und Mühe zu nehmen, um die rand () - Funktionalität zu untersuchen, zu untersuchen und ordnungsgemäß zu implementieren. Nur meine zwei Cent. Viel Glück bei Ihren Bemühungen ...
quelle
rand()
gibt nicht an, welcher Startwert verwendet werden soll. Der Standard legt fest , dass ein Pseudozufallsgenerator verwendet wird, nicht eine Beziehung zu irgendeiner Zeit. Es gibt auch keinen Hinweis auf die Qualität des Generators. Das eigentliche Problem ist eindeutig der Überlauf. Beachten Sie, dassrand()+1
verwendet wird, um zu vermeiden0
;rand()
gibt keinen negativen Wert zurück. Entschuldigung, aber Sie haben den Punkt hier verpasst. Es geht nicht um die Qualität des PRNG. .../dev/random
ein gutes PRNG zu verwenden und anschließend zu verwenden (nicht sicher über die Qualitätrand()
von glibc) oder das Gerät weiter zu verwenden - und zu riskieren, dass Ihre Anwendung blockiert, wenn nicht genügend Entropie verfügbar ist. Der Versuch, Ihre Entropie in die Anwendung zu bringen, ist möglicherweise eine Sicherheitslücke, da diese möglicherweise leichter anzugreifen ist. Und jetzt kommt es zum Härten - nicht hier