Ich habe immer gedacht, Zufallszahlen würden zwischen null und eins liegen, ohne1
, dh es handelt sich um Zahlen aus dem halboffenen Intervall [0,1]. Die Dokumentation auf cppreference.com von std::generate_canonical
bestätigt dies.
Wenn ich jedoch das folgende Programm ausführe:
#include <iostream>
#include <limits>
#include <random>
int main()
{
std::mt19937 rng;
std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
rng.seed(sequence);
rng.discard(12 * 629143 + 6);
float random = std::generate_canonical<float,
std::numeric_limits<float>::digits>(rng);
if (random == 1.0f)
{
std::cout << "Bug!\n";
}
return 0;
}
Es gibt mir die folgende Ausgabe:
Bug!
dh es erzeugt mir ein perfektes 1
, was Probleme bei meiner MC-Integration verursacht. Ist das ein gültiges Verhalten oder liegt ein Fehler auf meiner Seite vor? Dies ergibt die gleiche Ausgabe mit G ++ 4.7.3
g++ -std=c++11 test.c && ./a.out
und klirren 3.3
clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out
Wie kann ich vermeiden, wenn dies korrekt ist 1
?
Edit 1 : G ++ von git scheint unter dem gleichen Problem zu leiden. ich bin on
commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date: Mon Sep 1 08:26:51 2014 +0000
und Kompilieren mit ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.out
ergibt die gleiche Ausgabe, ldd
ergibt
linux-vdso.so.1 (0x00007fff39d0d000)
libstdc++.so.6 => /home/cschwan/temp/prefix/lib64/libstdc++.so.6 (0x00007f123d785000)
libm.so.6 => /lib64/libm.so.6 (0x000000317ea00000)
libgcc_s.so.1 => /home/cschwan/temp/prefix/lib64/libgcc_s.so.1 (0x00007f123d54e000)
libc.so.6 => /lib64/libc.so.6 (0x000000317e600000)
/lib64/ld-linux-x86-64.so.2 (0x000000317e200000)
Bearbeiten 2 : Ich habe das Verhalten hier gemeldet: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176
Bearbeiten 3 : Das Clang-Team scheint sich des Problems bewusst zu sein: http://llvm.org/bugs/show_bug.cgi?id=18767
1.f == 1.f
in allen Fällen (in welchen Fällen gibt es das? Ich habe noch nicht einmal Variablen gesehen1.f == 1.f
; hier gibt es nur einen Fall:1.f == 1.f
und das ist immer sotrue
). Bitte verbreiten Sie diesen Mythos nicht weiter. Gleitkomma-Vergleiche sind immer genau.abs(random - 1.f) < numeric_limits<float>::epsilon
überprüft, ob das Ergebnis nahe bei 1,0 liegt , was in diesem Zusammenhang völlig falsch ist: Es gibt Zahlen nahe 1,0, die hier gültige Ergebnisse sind, nämlich alle, die kleiner als 1,0 sind.Antworten:
Das Problem liegt in der Zuordnung von der Codomäne von
std::mt19937
(std::uint_fast32_t
) zufloat
; Der vom Standard beschriebene Algorithmus liefert falsche Ergebnisse (die nicht mit der Beschreibung der Ausgabe des Algorithmus übereinstimmen), wenn ein Genauigkeitsverlust auftritt, wenn der aktuelle IEEE754-Rundungsmodus nicht rund auf negativ bis unendlich ist (beachten Sie, dass der Standardwert rund ist zum nächsten).Die 7549723-Ausgabe von mt19937 mit Ihrem Startwert ist 4294967257 (
0xffffffd9u
), was, wenn auf 32-Bit-Float gerundet, ergibt0x1p+32
, was dem Maximalwert von mt19937, 4294967295 (0xffffffffu
) entspricht, wenn dies ebenfalls auf 32-Bit-Float gerundet wird.Der Standard könnte ein korrektes Verhalten sicherstellen, wenn er spezifiziert, dass beim Konvertieren von der Ausgabe des URNG in die
RealType
von einegenerate_canonical
Rundung in Richtung einer negativen Unendlichkeit durchgeführt werden soll; Dies würde in diesem Fall zu einem korrekten Ergebnis führen. Als QOI wäre es gut für libstdc ++, diese Änderung vorzunehmen.Mit dieser Änderung
1.0
wird nicht mehr generiert; Stattdessen werden die Grenzwerte0x1.fffffep-N
für0 < N <= 8
häufiger generiert (ungefähr2^(8 - N - 32)
proN
, abhängig von der tatsächlichen Verteilung von MT19937).Ich würde empfehlen , nicht zu verwenden ,
float
mitstd::generate_canonical
direkt; generiere lieber die Zahl indouble
und runde dann in Richtung negative Unendlichkeit:Dieses Problem kann auch auftreten bei
std::uniform_real_distribution<float>
; Die Lösung ist dieselbe, um die Verteilung aufdouble
das Ergebnis zu spezialisieren und es auf eine negative Unendlichkeit in zu rundenfloat
.quelle
sin(x)
, wirklich der Sinus von (π / Math.PI) mal x ist. Die Leute, die Java pflegen, bestehen darauf, dass es besser ist, eine langsame mathematische Routine zu haben, die berichtet, dass der Sinus von Math.PI der Unterschied zwischen π und Math.PI ist, als einen Wert zu melden, der etwas niedriger ist, ungeachtet dessen, dass dies in 99% der Anwendungen der Fall ist wäre besser ...std::uniform_real_distribution<float>
das gleiche Problem als Folge davon auftritt. (Damit Personen, die nach uniform_real_distribution suchen, diese Frage / Antwort erhalten).generate_canonical
eine Zahl im Bereich generiert werden sollte[0,1)
und es sich um einen Fehler handelt, bei dem gelegentlich 1,0 generiert wird, wäre eine Rundung auf Null nicht genauso effektiv?Nach dem Standard
1.0
ist nicht gültig.quelle
Ich bin gerade auf eine ähnliche Frage gestoßen
uniform_real_distribution
, und hier ist, wie ich den sparsamen Wortlaut des Standards zu diesem Thema interpretiere:Der Standard definiert mathematische Funktionen immer in Bezug auf Mathematik , niemals in Bezug auf IEEE-Gleitkomma (da der Standard immer noch vorgibt, dass Gleitkomma möglicherweise nicht IEEE-Gleitkomma bedeutet). Jedes Mal, wenn Sie mathematische Formulierungen im Standard sehen, handelt es sich um echte Mathematik , nicht um IEEE.
Der Standard sagt, dass beide
uniform_real_distribution<T>(0,1)(g)
undgenerate_canonical<T,1000>(g)
Werte im halboffenen Bereich zurückgeben sollten [0,1]. Dies sind jedoch mathematische Werte. Wenn Sie eine reelle Zahl im halboffenen Bereich [0,1] nehmen und sie als IEEE-Gleitkomma darstellen, ist dies ein erheblicher Bruchteil der Zeit, auf die sie aufgerundet wirdT(1.0)
.Wann
T
istfloat
(24 Mantissenbits), erwarten wiruniform_real_distribution<float>(0,1)(g) == 1.0f
ungefähr 1 in 2 ^ 25 mal. Mein Brute-Force-Experiment mit libc ++ bestätigt diese Erwartung.Beispielausgabe:
Wann
T
istdouble
(53 Mantissenbits), erwarten wiruniform_real_distribution<double>(0,1)(g) == 1.0
ungefähr 1 in 2 ^ 54 mal. Ich habe nicht die Geduld, diese Erwartung zu testen. :) :)Mein Verständnis ist, dass dieses Verhalten in Ordnung ist. Es kann unser Gefühl der "halboffenen Fremdheit" verletzen, dass eine Verteilung, die behauptet, Zahlen "kleiner als 1,0" zurückzugeben, tatsächlich Zahlen zurückgeben kann, die gleich sind
1.0
; aber das sind zwei verschiedene Bedeutungen von "1.0", sehen Sie? Der erste ist der mathematische 1.0; Die zweite ist die IEEE-Gleitkommazahl mit einfacher Genauigkeit1.0
. Und wir haben jahrzehntelang gelernt, Gleitkommazahlen nicht auf exakte Gleichheit zu vergleichen.Welchen Algorithmus Sie auch immer in die Zufallszahlen einspeisen, ist egal, ob er manchmal genau wird
1.0
. Mit einer Gleitkommazahl können Sie nichts anderes tun als mathematische Operationen. Sobald Sie eine mathematische Operation ausführen, muss sich Ihr Code mit Rundungen befassen. Auch wenn Sie könnten berechtigterweise davon ausgehen , dassgenerate_canonical<float,1000>(g) != 1.0f
Sie nach wie vor nicht in der Lage sein , das zu übernehmengenerate_canonical<float,1000>(g) + 1.0f != 2.0f
- wegen der Rundung. Sie können einfach nicht davon wegkommen; Warum sollten wir in dieser einzigen Instanz so tun, als ob Sie es könnten?quelle
1.0f
aber das ist nur unvermeidlich, wenn Sie sie in IEEE-Floats umwandeln. Wenn Sie rein mathematische Ergebnisse wünschen, verwenden Sie ein symbolisches Berechnungssystem. Wenn Sie versuchen, IEEE-Gleitkommawerte zur Darstellung von Zahlen innerhalbeps
von 1 zu verwenden, befinden Sie sich in einem Zustand der Sünde.canonical - 1.0f
. Für jeden darstellbaren Schwimmer in[0, 1.0)
,x-1.0f
nicht Null ist . Mit genau 1.0f können Sie eine Division durch Null anstelle eines sehr kleinen Divisors erhalten.