Quel est le meilleur moyen de générer un float aléatoire en C #?
Mise à jour: je veux des nombres à virgule flottante aléatoires de float.Minvalue à float.Maxvalue. J'utilise ces chiffres lors de tests unitaires de certaines méthodes mathématiques.
Meilleure approche, pas de valeur folle, distribuée par rapport aux intervalles pouvant être représentés sur la ligne numérique à virgule flottante (supprimée "uniforme", par rapport à une ligne numérique continue, elle est résolument non uniforme):
static float NextFloat(Random random)
{
double mantissa = (random.NextDouble() * 2.0) - 1.0;
// choose -149 instead of -126 to also generate subnormal floats (*)
double exponent = Math.Pow(2.0, random.Next(-126, 128));
return (float)(mantissa * exponent);
}
(*) ... vérifier ici pour les flotteurs inférieurs à la normale
Attention: génère aussi l'infini positif! Choisissez l'exposant de 127 pour être du côté sûr.
Une autre approche qui vous donnera des valeurs insensées (distribution uniforme des motifs de bits), potentiellement utiles pour le fuzzing:
static float NextFloat(Random random)
{
var buffer = new byte[4];
random.NextBytes(buffer);
return BitConverter.ToSingle(buffer,0);
}
Celle-ci constitue une amélioration par rapport à la version précédente. Elle ne crée pas de valeurs "folles" (ni les infinis ni NaN) et reste rapide (également répartie par rapport aux intervalles pouvant être représentés sur la droite numérique à virgule flottante):
public static float Generate(Random prng)
{
var sign = prng.Next(2);
var exponent = prng.Next((1 << 8) - 1); // do not generate 0xFF (infinities and NaN)
var mantissa = prng.Next(1 << 23);
var bits = (sign << 31) + (exponent << 23) + mantissa;
return IntBitsToFloat(bits);
}
private static float IntBitsToFloat(int bits)
{
unsafe
{
return *(float*) &bits;
}
}
Approche la moins utile:
static float NextFloat(Random random)
{
// Not a uniform distribution w.r.t. the binary floating-point number line
// which makes sense given that NextDouble is uniform from 0.0 to 1.0.
// Uniform w.r.t. a continuous number line.
//
// The range produced by this method is 6.8e38.
//
// Therefore if NextDouble produces values in the range of 0.0 to 0.1
// 10% of the time, we will only produce numbers less than 1e38 about
// 10% of the time, which does not make sense.
var result = (random.NextDouble()
* (Single.MaxValue - (double)Single.MinValue))
+ Single.MinValue;
return (float)result;
}
Ligne de numéro en virgule flottante de: Manuel du développeur de logiciels d’architecture Intel Volume 1: Architecture de base. L'axe des ordonnées est logarithmique (base 2), car les nombres à virgule flottante binaire consécutifs ne diffèrent pas linéairement.
Y a-t-il une raison de ne pas utiliser Random.NextDouble
puis de transtyper en float
? Cela vous donnera un float entre 0 et 1.
Si vous voulez une forme différente du "meilleur", vous devez spécifier vos besoins. Notez que Random
ne devrait pas être utilisé pour des questions sensibles telles que la finance ou la sécurité - et vous devriez généralement réutiliser une instance existante dans votre application, ou une instance par thread (car Random
n'est pas thread-safe).
EDIT: Comme suggéré dans les commentaires, pour convertir ceci en une plage de float.MinValue
, float.MaxValue
:
// Perform arithmetic in double type to avoid overflowing
double range = (double) float.MaxValue - (double) float.MinValue;
double sample = rng.NextDouble();
double scaled = (sample * range) + float.MinValue;
float f = (float) scaled;
EDIT: Maintenant, vous avez dit que c'était pour les tests unitaires, je ne suis pas sûr que ce soit une approche idéale. Vous devriez probablement plutôt tester avec des valeurs concrètes - en vous assurant de tester avec des échantillons dans chacune des catégories pertinentes - infinis, NaNs, nombres dénormaux, très grands nombres, zéro, etc.
Encore une version ... (je pense que celle-ci est plutôt bonne)
static float NextFloat(Random random)
{
(float)(float.MaxValue * 2.0 * (Rand.NextDouble()-0.5));
}
//inline version
float myVal = (float)(float.MaxValue * 2.0 * (Rand.NextDouble()-0.5));
Je pense que ce...
Et encore une version ... (pas aussi bien mais poster quand même)
static float NextFloat(Random random)
{
return float.MaxValue * ((Rand.Next() / 1073741824.0f) - 1.0f);
}
//inline version
float myVal = (float.MaxValue * ((Rand.Next() / 1073741824.0f) - 1.0f));
Je pense que ce...
Test de la plupart des fonctions de cette page: (i7, release, sans debug, 2 ^ 28 boucles)
Sunsetquest1: min: 3.402823E+38 max: -3.402823E+38 time: 3096ms
SimonMourier: min: 3.402823E+38 max: -3.402819E+38 time: 14473ms
AnthonyPegram:min: 3.402823E+38 max: -3.402823E+38 time: 3191ms
JonSkeet: min: 3.402823E+38 max: -3.402823E+38 time: 3186ms
Sixlettervar: min: 1.701405E+38 max: -1.701410E+38 time: 19653ms
Sunsetquest2: min: 3.402823E+38 max: -3.402823E+38 time: 2930ms
J'ai adopté une approche légèrement différente de celle des autres
static float NextFloat(Random random)
{
double val = random.NextDouble(); // range 0.0 to 1.0
val -= 0.5; // expected range now -0.5 to +0.5
val *= 2; // expected range now -1.0 to +1.0
return float.MaxValue * (float)val;
}
Les commentaires expliquent ce que je fais. Obtenez le prochain double, convertissez ce nombre en une valeur comprise entre -1 et 1 puis multipliez-le par float.MaxValue
.
Voici une autre manière que je propose: Supposons que vous souhaitiez un flottant compris entre 5,5 et 7, avec 3 décimales.
float myFloat;
int myInt;
System.Random rnd = new System.Random();
void GenerateFloat()
{
myInt = rnd.Next(1, 2000);
myFloat = (myInt / 1000) + 5.5f;
}
Ainsi, vous obtiendrez toujours un nombre supérieur à 5,5 et un nombre inférieur à 7.
Je préfère utiliser le code suivant pour générer un nombre décimal allant jusqu'à la première virgule. vous pouvez copier-coller la 3ème ligne pour ajouter plus de nombres après le point décimal en ajoutant ce nombre dans la chaîne "combiné". Vous pouvez définir les valeurs minimale et maximale en modifiant les valeurs 0 et 9 sur votre valeur préférée.
Random r = new Random();
string beforePoint = r.Next(0, 9).ToString();//number before decimal point
string afterPoint = r.Next(0,9).ToString();//1st decimal point
//string secondDP = r.Next(0, 9).ToString();//2nd decimal point
string combined = beforePoint+"."+afterPoint;
decimalNumber= float.Parse(combined);
Console.WriteLine(decimalNumber);
J'espère que cela vous a aidé.
Une autre solution consiste à faire ceci:
static float NextFloat(Random random)
{
float f;
do
{
byte[] bytes = new byte[4];
random.NextBytes(bytes);
f = BitConverter.ToSingle(bytes, 0);
}
while (float.IsInfinity(f) || float.IsNaN(f));
return f;
}