Question

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

Était-ce utile?

La solution

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 un "n'importe quel entier" aléatoire. valeur, qui, je pense, est 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);
}

Autres conseils

Vous devriez normalement vous attendre à ce qu'un générateur de nombres aléatoires génère 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: discrète uniformément aléatoire et < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer"> continuellement uniforme au hasard .

Discrètement, uniformément aléatoire est logique pour un générateur de nombres aléatoires ayant un nombre fini de résultats possibles différents. 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.

Un aléatoire continuellement uniforme 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 qui est en base 10), on peut se demander quelle est la définition correcte de uniformément aléatoire:

D'une part, le nombre à virgule flottante étant 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 en gros ce que de Jon Skeet et L'implémentation deJohn Leidegren le fait.

D’autre part, on pourrait faire valoir que, dans la mesure où un nombre à virgule flottante est censé ê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 GNA réel est en réalité discret. C'est le comportement que vous obtenez de Random.NextDouble (), où - même s'il y a environ 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 uniformément distribuée de manière continue.

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 simple 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 veillons à ce que les valeurs d'échelle soient 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.

Voici une implémentation aléatoire décimale avec une plage 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);
}

Je sais que c’est une vieille question, mais le problème de distribution décrit par Rasmus Faber n'arrivait pas à me gêner, alors je suis venu avec le suivant. Je n’ai pas examiné en détail la implémentation NextInt32 fournie par Jon Skeet et je suppose (en espérant) qu’elle a la même distribution que 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);
}

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

var rand = new Random();
var item = new decimal(rand.NextDouble());

Je suis perplexe avec cela pendant un moment. C’est ce que je pourrais faire de mieux:

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

Modifier: comme indiqué dans les commentaires, lo, mid et hi ne peuvent jamais contenir int.MaxValue, de sorte que la plage complète de nombres décimaux n'est pas possible.

voilà ... utilise la bibliothèque cryptée 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);
    }

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

MathNet.Numerics, Distributions de probabilités et 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és 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, vous pouvez créer vos instances RNG en tant que thread-safe si vous en avez besoin.

Très pratique en effet!

C’est une vieille question, mais pour ceux qui la lisent, pourquoi réinventer la roue?

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

Pour être honnête, je ne crois pas que le format interne de la décimale C # fonctionne de la manière dont beaucoup de gens pensent. Pour cette raison, 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 leur stockage 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 de la décimale sur 96 bits, tout en laissant la balance inchangée.

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

D'autres expériences ont prouvé que le code suivant définissait 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 trouvé 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 ravi que d'autres personnes le vérifient pour rechercher 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 trillions en faveur d'un certaine gamme de nombres).

La première fonction NextDecimal () devrait 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 le mot croire, je ne fais pas confiance à mes calculs). Cela devrait empêcher les fonctions d’arrondir la valeur de retour à 1,0 m.

La deuxième fonction NextDecimal () doit fonctionner de la même manière que la fonction Random.Next (), uniquement 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, je pense donc avoir raison, mais encore une fois, je ne l’ai pas testé - vous voudrez donc vous assurer qu’il fonctionne correctement avant de vous y fier.

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

Je voulais générer "aléatoire". décimales 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

.

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

Je ne me suis pas soucié de l'uniformité ou de la précision des nombres générés, donc les 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 particulier, je cherchais 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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top