web-dev-qa-db-fra.com

Générer une décimale aléatoire en C #

Comment puis-je obtenir un System.Decimal aléatoire? System.Random ne le supporte pas directement.

57
Daniel Ballinger

EDIT: ancienne version supprimée

Ceci est similaire à la version de Daniel, mais donnera la gamme complète. Il introduit également une nouvelle méthode d’extension pour obtenir une valeur aléatoire "n’importe quel entier", ce qui me semble très pratique.

Notez que la distribution des nombres décimaux ici n'est pas uniforme .

/// <summary>
/// Returns an Int32 with a random value across the entire range of
/// possible values.
/// </summary>
public static int NextInt32(this Random rng)
{
     int firstBits = rng.Next(0, 1 << 4) << 28;
     int lastBits = rng.Next(0, 1 << 28);
     return firstBits | lastBits;
}

public static decimal NextDecimal(this Random rng)
{
     byte scale = (byte) rng.Next(29);
     bool sign = rng.Next(2) == 1;
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.NextInt32(),
                        sign,
                        scale);
}
42
Jon Skeet

Vous vous attendez normalement d'un générateur de nombres aléatoires à générer non seulement des nombres aléatoires, mais également des nombres générés aléatoirement.

Il existe deux définitions de uniformément aléatoire: discret uniformément aléatoire et continu uniformément aléatoire .

Discrètement, uniformément aléatoire est logique pour un générateur de nombres aléatoires qui a un nombre fini de résultats possibles. Par exemple, générer un entier compris entre 1 et 10. Vous vous attendez alors à ce que la probabilité d'obtenir 4 soit la même chose que d'obtenir 7.

De manière continuellement uniforme et aléatoire a du sens lorsque le générateur de nombres aléatoires génère des nombres dans une plage. Par exemple, un générateur qui génère un nombre réel compris entre 0 et 1. Vous vous attendez alors à ce que la probabilité d'obtenir un nombre compris entre 0 et 0,5 soit identique à celle obtenue entre 0,5 et 1.

Lorsqu'un générateur de nombres aléatoires génère des nombres à virgule flottante (ce qui est fondamentalement ce qu'est un System.Decimal - c'est juste une virgule flottante dont la base 10), on peut se demander quelle est la définition correcte de uniformément aléatoire:

D'une part, comme le nombre à virgule flottante est représenté par un nombre fixe de bits dans un ordinateur, il est évident qu'il existe un nombre fini de résultats possibles. On pourrait donc soutenir que la distribution appropriée est une distribution continue discrète, chaque nombre représentable ayant la même probabilité. C’est essentiellement ce que l’application de Jon Skeet et John Leidegren fait.

D’autre part, on pourrait faire valoir que, dans la mesure où un nombre à virgule flottante est supposé être une approximation d’un nombre réel, il serait préférable de tenter d’approximer le comportement d’un générateur de nombres aléatoires continus - même si le RNG est réel. réellement discret. C'est le comportement que vous obtenez de Random.NextDouble (), où - même s'il y a approximativement autant de nombres représentables dans la plage 0.00001-0.00002 que dans la plage 0.8-0.9, vous avez mille fois plus de chances d'obtenir un nombre dans la deuxième plage - comme on peut s'y attendre.

Ainsi, une implémentation appropriée de Random.NextDecimal () devrait probablement être distribuée de manière continue et uniforme.

Voici une simple variante de la réponse de Jon Skeet uniformément répartie entre 0 et 1 (je réutilise sa méthode d'extension NextInt32 ()):

public static decimal NextDecimal(this Random rng)
{
     return new decimal(rng.NextInt32(), 
                        rng.NextInt32(),
                        rng.Next(0x204FCE5E),
                        false,
                        0);
}

Vous pouvez également discuter de la manière d’obtenir une distribution uniforme sur l’ensemble des décimales. Il existe probablement un moyen plus facile de le faire, mais cette légère modification de La réponse de John Leidegren devrait produire une distribution relativement uniforme:

private static int GetDecimalScale(Random r)
{
  for(int i=0;i<=28;i++){
    if(r.NextDouble() >= 0.1)
      return i;
  }
  return 0;
}

public static decimal NextDecimal(this Random r)
{
    var s = GetDecimalScale(r);
    var a = (int)(uint.MaxValue * r.NextDouble());
    var b = (int)(uint.MaxValue * r.NextDouble());
    var c = (int)(uint.MaxValue * r.NextDouble());
    var n = r.NextDouble() >= 0.5;
    return new Decimal(a, b, c, n, s);
}

En gros, nous nous assurons que les valeurs d'échelle sont choisies proportionnellement à la taille de la plage correspondante.

Cela signifie que nous devrions obtenir une échelle de 0 à 90% du temps - puisque cette plage contient 90% de la plage possible - une échelle de 1 à 9% du temps, etc.

La mise en œuvre pose encore quelques problèmes, car elle prend en compte le fait que certains nombres ont plusieurs représentations - mais cela devrait être beaucoup plus proche d’une distribution uniforme que les autres mises en œuvre.

9
Rasmus Faber

Voici l’implémentation aléatoire décimale avec Range qui me convient parfaitement.

public static decimal NextDecimal(this Random rnd, decimal from, decimal to)
{
    byte fromScale = new System.Data.SqlTypes.SqlDecimal(from).Scale;
    byte toScale = new System.Data.SqlTypes.SqlDecimal(to).Scale;

    byte scale = (byte)(fromScale + toScale);
    if (scale > 28)
        scale = 28;

    decimal r = new decimal(rnd.Next(), rnd.Next(), rnd.Next(), false, scale);
    if (Math.Sign(from) == Math.Sign(to) || from == 0 || to == 0)
        return decimal.Remainder(r, to - from) + from;

    bool getFromNegativeRange = (double)from + rnd.NextDouble() * ((double)to - (double)from) < 0;
    return getFromNegativeRange ? decimal.Remainder(r, -from) + from : decimal.Remainder(r, to);
}
6
Danil

Je sais que c’est une vieille question, mais le problème de distribution décrit par Rasmus Faber continuait à me gêner et j’ai donc proposé ce qui suit. Je n'ai pas examiné en profondeur la implémentation NextInt32 fournie par Jon Skeet et je suppose (espérant) que sa distribution est identique à celle de Random.Next () .

//Provides a random decimal value in the range [0.0000000000000000000000000000, 0.9999999999999999999999999999) with (theoretical) uniform and discrete distribution.
public static decimal NextDecimalSample(this Random random)
{
    var sample = 1m;
    //After ~200 million tries this never took more than one attempt but it is possible to generate combinations of a, b, and c with the approach below resulting in a sample >= 1.
    while (sample >= 1)
    {
        var a = random.NextInt32();
        var b = random.NextInt32();
        //The high bits of 0.9999999999999999999999999999m are 542101086.
        var c = random.Next(542101087);
        sample = new Decimal(a, b, c, false, 28);
    }
    return sample;
}

public static decimal NextDecimal(this Random random)
{
    return NextDecimal(random, decimal.MaxValue);
}

public static decimal NextDecimal(this Random random, decimal maxValue)
{
    return NextDecimal(random, decimal.Zero, maxValue);
}

public static decimal NextDecimal(this Random random, decimal minValue, decimal maxValue)
{
    var nextDecimalSample = NextDecimalSample(random);
    return maxValue * nextDecimalSample + minValue * (1 - nextDecimalSample);
}
4
Bryan Loeper

C'est aussi, grâce au pouvoir des choses faciles, à faire:

var Rand = new Random();
var item = new decimal(Rand.NextDouble());
3
Adam Wells

Je suis perplexe avec cela pendant un moment. C'est le meilleur que je pourrais trouver:

public class DecimalRandom : Random
    {
        public override decimal NextDecimal()
        {
            //The low 32 bits of a 96-bit integer. 
            int lo = this.Next(int.MinValue, int.MaxValue);
            //The middle 32 bits of a 96-bit integer. 
            int mid = this.Next(int.MinValue, int.MaxValue);
            //The high 32 bits of a 96-bit integer. 
            int hi = this.Next(int.MinValue, int.MaxValue);
            //The sign of the number; 1 is negative, 0 is positive. 
            bool isNegative = (this.Next(2) == 0);
            //A power of 10 ranging from 0 to 28. 
            byte scale = Convert.ToByte(this.Next(29));

            Decimal randomDecimal = new Decimal(lo, mid, hi, isNegative, scale);

            return randomDecimal;
        }
    }

Edit: Comme indiqué dans les commentaires, lo, mid et hi ne peuvent jamais contenir int.MaxValue, ainsi la gamme complète de Decimals n’est pas possible.

2
Daniel Ballinger

Pour être honnête, je ne crois pas que le format interne de la décimale C # fonctionne comme beaucoup de gens le pensent. Pour cette raison, au moins certaines des solutions présentées ici ne sont peut-être pas valables ou ne fonctionnent pas toujours. Considérez les 2 nombres suivants et comment ils sont stockés au format décimal:

0.999999999999999m
Sign: 00
96-bit integer: 00 00 00 00 FF 7F C6 A4 7E 8D 03 00
Scale: 0F

et

0.9999999999999999999999999999m
Sign: 00
96-bit integer: 5E CE 4F 20 FF FF FF 0F 61 02 25 3E
Scale: 1C

Notez bien en quoi l’échelle est différente, mais les deux valeurs sont presque identiques, c’est-à-dire qu’elles sont toutes deux inférieures à 1 sur une fraction infime. Il semble que ce soit l'échelle et le nombre de chiffres qui ont une relation directe. À moins que quelque chose ne me manque, cela devrait jeter une clé à molette dans la plupart des codes qui altèrent la partie entière à 96 bits d'un nombre décimal, mais la balance reste inchangée.

En expérimentant, j'ai trouvé que le nombre 0,9999999999999999999999999999m, qui compte 28 neuf, correspond au nombre maximal de neuf possible avant que la décimale ne soit arrondie à 1,0 m.

Des expériences ultérieures ont prouvé que le code suivant attribue à la variable "Dec" la valeur 0.9999999999999999999999999999m:

double DblH = 0.99999999999999d;
double DblL = 0.99999999999999d;
decimal Dec = (decimal)DblH + (decimal)DblL / 1E14m;

C'est à partir de cette découverte que j'ai proposé les extensions de la classe Random qui peuvent être vues dans le code ci-dessous. Je crois que ce code est entièrement fonctionnel et en bon état de fonctionnement, mais je serais heureux que d’autres yeux le vérifient pour des erreurs. Je ne suis pas un statisticien, donc je ne peux pas dire si ce code produit une distribution vraiment uniforme des nombres décimaux, mais si je devais deviner, je dirais qu'il manque à la perfection, mais qu'il est extrêmement proche (comme dans 1 appel sur 51 tr certaine gamme de nombres).

La première fonction NextDecimal () doit produire des valeurs égales ou supérieures à 0,0 m et inférieures à 1,0 m. L'instruction do/while empêche RandH et RandL de dépasser la valeur 0.99999999999999d en effectuant une boucle jusqu'à ce qu'ils soient inférieurs à cette valeur. Je crois que les probabilités que cette boucle se répète sont de 1 sur 51 000 milliards (accent mis sur la Parole, croyez, je ne fais pas confiance à mes calculs). Cela devrait empêcher les fonctions d’arrondir la valeur de retour jusqu’à 1,0 m.

La deuxième fonction NextDecimal () doit fonctionner de la même manière que la fonction Random.Next (), mais avec des valeurs Decimal au lieu d’entiers. En fait, je n'ai pas utilisé cette seconde fonction NextDecimal () et je ne l'ai pas testée. C'est assez simple, donc je pense avoir bien compris, mais encore une fois, je ne l'ai pas testé - vous voudrez donc vous assurer qu'il fonctionne correctement avant de pouvoir vous en servir.

public static class ExtensionMethods {
    public static decimal NextDecimal(this Random rng) {
        double RandH, RandL;
        do {
            RandH = rng.NextDouble();
            RandL = rng.NextDouble();
        } while((RandH > 0.99999999999999d) || (RandL > 0.99999999999999d));
        return (decimal)RandH + (decimal)RandL / 1E14m;
    }
    public static decimal NextDecimal(this Random rng, decimal minValue, decimal maxValue) {
        return rng.NextDecimal() * (maxValue - minValue) + minValue;
    }
}
1
Darin
static decimal GetRandomDecimal()
    {

        int[] DataInts = new int[4];
        byte[] DataBytes = new byte[DataInts.Length * 4];

        // Use cryptographic random number generator to get 16 bytes random data
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

        do
        {
            rng.GetBytes(DataBytes);

            // Convert 16 bytes into 4 ints
            for (int index = 0; index < DataInts.Length; index++)
            {
                DataInts[index] = BitConverter.ToInt32(DataBytes, index * 4);
            }

            // Mask out all bits except sign bit 31 and scale bits 16 to 20 (value 0-31)
            DataInts[3] = DataInts[3] & (unchecked((int)2147483648u | 2031616));

          // Start over if scale > 28 to avoid bias 
        } while (((DataInts[3] & 1835008) == 1835008) && ((DataInts[3] & 196608) != 0));

        return new decimal(DataInts);
    }
    //end
1
Melvil Dewey

Consultez le lien suivant pour des implémentations toutes faites qui pourraient vous aider:

MathNet.Numerics, Nombres Aléatoires et Distributions de Probabilités

Les distributions étendues sont particulièrement intéressantes, construites sur les générateurs de nombres aléatoires (MersenneTwister, etc.) directement dérivées de System.Random, fournissant toutes des méthodes d’extension pratiques (par exemple, NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). Vous pouvez bien sûr simplement utiliser la valeur par défaut SystemRandomSource, qui est simplement System.Random agrémentée des méthodes d’extension.

Oh, et vous pouvez créer vos instances RNG comme thread-safe si vous en avez besoin.

Très pratique en effet!

C'est une vieille question, mais pour ceux qui ne font que la lire, pourquoi réinventer la roue?

1
Ben Stabile

voilà ... utilise la bibliothèque crypt pour générer quelques octets aléatoires, puis les convertit en valeur décimale ... voir MSDN pour le constructeur décimal

using System.Security.Cryptography;

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] randomNumber = new Byte[] { 0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(randomNumber);

    // convert the bytes to a decimal
    return new decimal(new int[] 
    { 
               0,                   // not used, must be 0
               randomNumber[0] % 29,// must be between 0 and 28
               0,                   // not used, must be 0
               randomNumber[1] % 2  // sign --> 0 == positive, 1 == negative
    } ) % (max+1);
}

révisé pour utiliser un constructeur décimal différent afin de donner une meilleure plage de nombres

public static decimal Next(decimal max)
{
    // Create a int array to hold the random values.
    Byte[] bytes= new Byte[] { 0,0,0,0 };

    RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();

    // Fill the array with a random value.
    Gen.GetBytes(bytes);
    bytes[3] %= 29; // this must be between 0 and 28 (inclusive)
    decimal d = new decimal( (int)bytes[0], (int)bytes[1], (int)bytes[2], false, bytes[3]);

        return d % (max+1);
    }
1
Muad'Dib

Je voulais générer des décimales "aléatoires" jusqu'à 9 décimales. Mon approche consistait simplement à générer un double et à le diviser pour les décimales.

int randomInt = rnd.Next(0, 100);

double randomDouble = rnd.Next(0, 999999999);
decimal randomDec = Convert.ToDecimal(randomint) + Convert.ToDecimal((randomDouble/1000000000));

le "randomInt" est le nombre avant la décimale, vous pouvez simplement mettre 0 . Pour réduire les points décimaux, supprimez simplement "9" de manière aléatoire et "0" de division

0
humudu

Étant donné que la question OP est très englobante et que je veux juste un System.Decimal aléatoire sans aucune restriction, voici une solution très simple qui a fonctionné pour moi.

Je ne me souciais d'aucun type d'uniformité ou de précision des nombres générés, donc d'autres réponses ici sont probablement meilleures si vous avez des restrictions, mais celle-ci fonctionne bien dans des cas simples.

Random rnd = new Random();
decimal val;
int decimal_places = 2;
val = Math.Round(new decimal(rnd.NextDouble()), decimal_places);

Dans mon cas spécifique, je recherchais une décimale aléatoire à utiliser comme chaîne d'argent. Ma solution complète était donc:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
0
James