J'ai toujours pensé que les nombres aléatoires se situeraient entre zéro et un, sans 1
, c'est-à-dire que ce sont des nombres de l'intervalle semi-ouvert [0,1). Le documentation sur cppreference.com of std::generate_canonical
le confirme.
Cependant, lorsque j'exécute le programme suivant:
#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;
}
Cela me donne la sortie suivante:
Bug!
c'est-à-dire qu'il me génère un parfait 1
, ce qui cause des problèmes dans mon intégration MC. Est-ce un comportement valide ou y a-t-il une erreur de mon côté? Cela donne la même sortie avec G ++ 4.7.3
g++ -std=c++11 test.c && ./a.out
et clang 3.3
clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out
S'il s'agit d'un comportement correct, comment puis-je éviter 1
?
Édition 1 : G ++ de git semble souffrir du même problème. Je suis sur
commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date: Mon Sep 1 08:26:51 2014 +0000
et compiler avec ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.out
donne la même sortie, ldd
donne
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)
Édition 2 : J'ai signalé le comportement ici: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176
Edit 3 : L'équipe de clang semble être consciente du problème: http://llvm.org/bugs/show_bug.cgi? id = 18767
Le problème réside dans le mappage du domaine de codage de std::mt19937
(std::uint_fast32_t
) Vers float
; l'algorithme décrit par la norme donne des résultats incorrects (incohérents avec sa description de la sortie de l'algorithme) lorsque la perte de précision se produit si le mode d'arrondi IEEE754 actuel est autre que arrondi à l'infini négatif (notez que la valeur par défaut est arrondie -au plus proche).
La sortie 7549723rd de mt19937 avec votre graine est 4294967257 (0xffffffd9u
) Qui, lorsqu'elle est arrondie à un flottant de 32 bits, donne 0x1p+32
, Qui est égale à la valeur maximale de mt19937, 4294967295 (0xffffffffu
) Quand il est également arrondi à un flottant de 32 bits.
La norme pourrait garantir un comportement correct si elle devait spécifier que lors de la conversion de la sortie de l'URNG en RealType
de generate_canonical
, L'arrondi doit être effectué vers l'infini négatif; cela donnerait un résultat correct dans ce cas. En tant que QOI, il serait bon que libstdc ++ fasse ce changement.
Avec cette modification, 1.0
Ne sera plus généré; au lieu de cela, les valeurs limites 0x1.fffffep-N
pour 0 < N <= 8
seront générées plus souvent (environ 2^(8 - N - 32)
par N
, selon la distribution réelle de MT19937).
Je recommanderais de ne pas utiliser float
avec std::generate_canonical
Directement; générez plutôt le nombre dans double
puis arrondissez vers l'infini négatif:
double rd = std::generate_canonical<double,
std::numeric_limits<float>::digits>(rng);
float rf = rd;
if (rf > rd) {
rf = std::nextafter(rf, -std::numeric_limits<float>::infinity());
}
Ce problème peut également se produire avec std::uniform_real_distribution<float>
; la solution est la même, pour spécialiser la distribution sur double
et arrondir le résultat vers l'infini négatif dans float
.
Selon la norme, 1.0
n'est pas valide.
C++ 11 §26.5.7.2 Modèle de fonction generate_canonical
Chaque fonction instanciée à partir du modèle décrit dans cette section 26.5.7.2 mappe le résultat d'une ou plusieurs invocations d'un générateur de nombres aléatoires uniforme fourni
g
à un membre du RealType spécifié de sorte que, si les valeurs gje produits parg
sont uniformément distribués, les résultats de l'instanciation tj , ≤ tj <1, sont distribués aussi uniformément que possible comme spécifié ci-dessous.