Génération D'une décimale aléatoire en C#

Comment puis-je obtenir un Système aléatoire.Décimal? System.Random ne le supporte pas directement.

54
demandé sur abatishchev 2009-03-04 10:02:02

12 réponses

EDIT: suppression de l'ancienne version

c'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", qui je pense est pratique.

notez que la distribution des décimales 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);
}
40
répondu Jon Skeet 2017-11-19 16:53:13

vous vous attendez normalement d'un générateur de nombres aléatoires que non seulement il a généré des nombres aléatoires, mais que les nombres ont été uniformément générés au hasard.

il y a deux définitions de" Aléatoire uniforme": aléatoire discret et uniforme et aléatoire continu et uniforme .

Discrètement uniformément aléatoire de sens que pour un générateur de nombre aléatoire qui a un nombre fini de résultat. Par exemple, générer un entier entre 1 et 10. Vous vous attendriez alors à ce que la probabilité d'obtenir 4 est la même que d'obtenir 7.

en Continu uniformément aléatoire de sens que si le générateur de nombre aléatoire génère des nombres dans une plage. Par exemple un générateur qui génère un nombre réel entre 0 et 1. Vous vous attendriez alors à ce que la probabilité d'obtenir un nombre entre 0 et 0,5 est la même que d'obtenir un nombre entre 0,5 et 1.

quand un générateur de nombres aléatoires produit des nombres à virgule flottante (qui est essentiellement ce Qu'un système.Décimal est-il est juste flottant-point qui base 10), il est discutable ce que la définition correcte de uniformément aléatoire est:

d'une part, puisque le nombre à virgule flottante est représenté par un nombre fixe de bits dans un ordinateur, il est évident qu'il y a un nombre fini de résultats possibles. Donc, on pourrait dire que la distribution est discret distribution continue avec chaque nombre représentatif ayant la même probabilité. C'est essentiellement ce que fait la mise en œuvre de de Jon Skeet et de John Leidegren .

d'autre part, on pourrait faire valoir que depuis un nombre flottant est censé être une approximation à un nombre réel, nous serions mieux en essayant d'approcher le comportement d'un générateur de nombre aléatoire continu - même si sont RNG réel est en fait discret. C'est le comportement que vous obtenez au hasard.NextDouble (), où-bien qu'il y ait approximativement autant de nombres représentables dans la gamme 0.00001-0.00002 que dans la gamme 0.8-0.9, vous êtes mille fois plus susceptibles d'obtenir un nombre dans la deuxième gamme - comme vous vous y attendriez.

donc une mise en œuvre correcte D'un Random.NextDecimal () devrait probablement être distribué de façon continue et uniforme.

voici une variation simple de la réponse de Jon Skeet qui est 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 pourriez également discuter de la façon d'obtenir une distribution uniforme sur toute la gamme des décimales. Il y a probablement une façon plus facile de le faire, mais cette légère modification de 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 choisis proportionnellement à la taille de la gamme 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 19% du temps, etc.

il y a encore quelques problèmes avec la mise en œuvre, car il tient compte du fait que certains nombres ont des représentations multiples - mais il devrait être beaucoup plus proche d'une distribution uniforme que l'autre application.

8
répondu Rasmus Faber 2017-05-23 10:31:31

voici la décimale aléatoire avec une implémentation de portée qui fonctionne très bien pour moi.

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
répondu Danil 2012-09-12 12:22:42

je sais que c'est une vieille question, mais le problème de distribution Rasmus Faber décrit a continué à me déranger donc je suis venu avec la suivante. Je n'ai pas examiné en profondeur la mise en œuvre de NextInt32 fournie par Jon Skeet et je suppose (en espérant) qu'elle a la même distribution que aléatoire.Suivant () .

//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
répondu Bryan Loeper 2017-05-23 12:03:04

c'est aussi, par la force de la facilité, de faire:

var rand = new Random();
var item = new decimal(rand.NextDouble());
3
répondu Adam Wells 2017-04-06 14:52:53

j'ai été un peu perplexe. C'est le mieux que j'ai pu 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 peut jamais contenir int.MaxValue donc la gamme complète de décimales n'est pas possible.

2
répondu Daniel Ballinger 2009-03-04 08:13:40

voilà... utilise la bibliothèque crypt pour générer quelques octets aléatoires, puis les convertit en une 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 pour donner une meilleure gamme 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
répondu Muad'Dib 2009-03-04 15:36:39

consultez le lien suivant pour des implémentations prêtes à l'emploi qui devraient vous aider:

MathNet.Des nombres, des Nombres Aléatoires et Distributions de Probabilité

les distributions étendues sont particulièrement intéressantes, construites sur les générateurs de nombres aléatoires (MersenneTwister, etc. directement dérivé du Système.Aléatoire, tous fournissant des méthodes d'extension pratiques (par ex. NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). Vous pouvez, bien sûr, juste utiliser le SystemRandomSource par défaut, qui est simplement système.Aléatoire embelli avec les 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 la lisent, pourquoi réinventer la roue?

1
répondu Ben Stabile 2014-02-05 01:40:57
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
répondu nerdenator 2014-03-01 03:01:21

pour être honnête, je ne crois pas que le format interne du C# décimal fonctionne comme beaucoup le pensent. Pour cette raison, au moins certaines des solutions présentées ici sont peut-être invalides ou peuvent ne pas fonctionner de façon constante. Considérez les 2 nombres suivants et comment ils sont stockés dans le 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

Prendre note de la façon dont l'échelle est différente, mais les deux valeurs sont à peu près les mêmes en ce qu'ils sont tous les deux inférieurs à 1 par une infime fraction. Il paraît que c'est l'échelle et le nombre de chiffres qui ont une relation directe. À moins que je ne manque quelque chose, cela devrait jeter une clé à molette dans la plupart des codes qui altère la partie entière de 96 bits d'une décimale mais laisse l'échelle inchangée.

dans l'expérimentation j'ai trouvé que le nombre 0.9999999999999999999999 m, qui a 28 nines, a le nombre maximum de nines possible avant la décimale sera arrondir à 1,0 m.

une expérimentation a prouvé le code suivant définit la variable "Dec" pour la valeur 0.9999999999999999999999999999 m:

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 aléatoire qui peuvent être vues dans le code ci-dessous. Je pense que ce code est pleinement fonctionnel et en bon état de marche, mais je serais heureux que d'autres yeux le vérifient pour des erreurs. Je ne suis pas statisticien donc je ne peux pas dire si ce code produit une distribution vraiment uniforme de décimales, mais si je devais deviner, je dirais qu'il échoue perfection mais est extrêmement proche (comme dans 1 appel sur 51 trillions favorisant une 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. La déclaration do/while empêche RandH et RandL de dépasser la valeur 0.999999999999 d en bouclant Jusqu'à ce qu'ils soient en dessous de cette valeur. Je crois que le les chances de cette boucle jamais répéter sont de 1 sur 51 trillions (accent sur le mot croire, Je ne fais pas confiance à mes maths). Cela devrait empêcher les fonctions d'arrondi, la valeur de retour jusqu'à 1,0 m.

la seconde fonction NextDecimal() doit fonctionner de la même manière que la fonction Random.Fonction Next (), uniquement avec des valeurs Décimales au lieu de nombres 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 que j'ai raison, mais encore une fois, je ne l'ai pas testé - donc vous voudrez s'assurer qu'il fonctionne correctement avant de compter sur elle.

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
répondu Darin 2015-11-22 06:57:04

je voulais générer des décimales" aléatoires " jusqu'à 9 décimales. Mon approche était de générer un double et de 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 juste mettre 0. Pour réduire les points décimaux il suffit de supprimer"9" s au hasard et " 0 "s en divisant

0
répondu humudu 2018-01-04 11:57:27

puisque la question OP est très englobante et veut juste un système aléatoire.Décimal sans aucune restriction, ci-dessous est une solution très simple qui a fonctionné pour moi.

Je ne me souciais pas de n'importe quel type d'uniformité ou de précision des nombres produits, ainsi d'autres réponses ici sont probablement mieux si vous avez quelques restrictions, mais celui-ci fonctionne très bien dans les cas simples.

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

dans mon cas précis, je cherchais une décimale aléatoire à utiliser comme une chaîne de caractères d'argent, de sorte que ma solution complète était:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
0
répondu James 2018-02-27 14:55:06