web-dev-qa-db-fra.com

Meilleure façon de générer un float aléatoire en C #

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.

48
KrisTrip

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. 

Comparison of distributions, logarithmic Y-axis

59
user7116

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.

24
Jon Skeet

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...

  • est le 2e plus rapide (voir points de repère)
  • est distribué uniformément

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...

  • est le plus rapide (voir points de repère)
  • est distribué de manière uniforme, car Next () étant une valeur aléatoire de 31 bits, il ne renverra que 2 ^ 31 valeurs. (50% des valeurs voisines auront la même valeur)

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
5
Sunsetquest

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.

1
Anthony Pegram

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.

0
Qedized

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é.

0
Waheed Sattar

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;
}
0
Simon Mourier