2010-07-29 15 views
40

Quelle est la meilleure façon de générer un flottant aléatoire en C#? Mise à jour: Je veux des nombres à virgule flottante de float.Minvalue à float.Maxvalue. J'utilise ces nombres dans les tests unitaires de certaines méthodes mathématiques.Meilleure façon de générer un flottant aléatoire en C#

+20

4.0 - aléatoire choisi par un jet de dés à virgule flottante. –

+5

@ JesseC.Slicer Vous avez oublié de fournir le lien: http://xkcd.com/221/ – jpmc26

Répondre

48

Meilleure approche, aucune valeur folle, distributed with respect to the representable intervals on the floating-point number line (enlevé « uniforme » que par rapport à une ligne numérique en continu, il est décidément non uniforme):

static float NextFloat(Random random) 
{ 
    double mantissa = (random.NextDouble() * 2.0) - 1.0; 
    double exponent = Math.Pow(2.0, random.Next(-126, 128)); 
    return (float)(mantissa * exponent); 
} 

une autre approche qui vous donnera des valeurs déments (répartition uniforme des motifs de bits), potentiellement utile pour fuzzing:

static float NextFloat(Random random) 
{ 
    var buffer = new byte[4]; 
    random.NextBytes(buffer); 
    return BitConverter.ToSingle(buffer,0); 
} 

approche 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 nombre à virgule flottante à partir de: Intel Architecture Software Developer's Manual Volume 1: Basic Architecture. L'axe des Y est logarithmique (base 2) en raison des nombres à virgule flottante binaires consécutives ne diffèrent pas de façon linéaire.

Comparison of distributions, logarithmic Y-axis

+0

Il ne semble pas y avoir de méthode BitConverter.GetSingle. Voulez-vous dire ToSingle? – KrisTrip

+0

Ce serait ce que je veux dire ... – user7116

+1

L'utilisation d'octets aléatoires peut facilement aboutir à une valeur NaN, et la distribution pourrait bien ne pas être uniforme. (Je ne voudrais pas prédire la distribution.) –

20

N'importe quelle raison pour ne pas utiliser Random.NextDouble, puis passer à float? Cela vous donnera un flottant entre 0 et 1.

Si vous voulez une forme différente de «meilleur», vous devrez spécifier vos besoins. Notez que Random ne devrait pas être utilisé pour des sujets sensibles tels que la finance ou la sécurité - et vous devriez généralement réutiliser une instance existante dans votre application, ou une par thread (car Random n'est pas thread-safe).

EDIT: Comme suggéré dans les commentaires, convertir à un éventail 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: Vous avez mentionné que cela est pour les tests unitaires, je ne suis pas sûr que c'est un approche idéale. Vous devriez probablement 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.

+0

Je pense que NextDouble fournit juste des valeurs de 0.0 à 1.0 et je veux plus de portée que cela (comme de float.MinValue à float.MaxValue). Je suppose que j'aurais dû spécifier :) – KrisTrip

+3

Il suffit de le multiplier par (MaxValue-MinValue) et lui ajouter MinValue? Donc, quelque chose comme Random.NextDouble * (float.MaxValue-float.MinValue) + float.MinValue – Xzhsh

+0

La distribution des valeurs qui produit est bimodale, je crois que cela a à voir avec la taille des valeurs dans la gamme. – user7116

1

Je pris une approche légèrement différente que d'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 double suivant, convertissez ce nombre à une valeur comprise entre -1 et 1, puis multipliez cela par float.MaxValue.

0

Une autre solution consiste à faire:

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; 
} 
5

Une version plus ... (je pense que celui-ci est assez bon)

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 2ème plus rapide (voir repères)
  • est réparti uniformément

Et une version plus ... (pas aussi bon, mais l'affichage de toute façon)

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

  • est le plus rapide (voir les tests de performance)
  • est distribué uniformément mais parce que Next() est une valeur aléatoire de 31 bits, il ne retournera que 2^31 valeurs. (50% des valeurs voisines aura la même valeur)

Test de la plupart des fonctions de cette page: (I7 libération, sans mise au point, 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 
+1

Etes-vous sûr que ce sera plus rapide parce que c'est une puissance de deux? Je suppose qu'il serait implicitement moulé à un flotteur * (qui ne représenterait pas exactement la valeur, puis utiliser la division flottante régulière). * – ideasman42

+0

Merci de remarquer que c'était une bonne prise! Vous avez raison de dire qu'il a été converti en un flottant en premier afin que le compilateur ne puisse pas utiliser une instruction shift. J'ai enlevé le "est plus rapide parce que c'est une puissance de deux". Comme vous pouvez le voir, j'ai fait plusieurs autres changements, y compris l'ajout d'une nouvelle fonction et aussi faire des repères. – Sunsetquest

0

Voici une autre méthode que j'ai trouvée: Disons que vous voulez obtenir un flottant 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; 
} 

De cette façon, vous obtiendrez toujours un plus grand nombre de 5,5 et un nombre inférieur à 7.

0

Je préfère utiliser le code suivant pour générer un nombre décimal à virgule décimale poing. 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ée". Vous pouvez définir la valeur minimum et maximum en changeant le 0 et le 9 à 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é.

Questions connexes