Domanda

Come posso ottenere un System.Decimal casuale? System.Random non lo supporta direttamente.

È stato utile?

Soluzione

EDIT: versione precedente rimossa

Questo è simile alla versione di Daniel, ma fornirà la gamma completa. Inoltre introduce un nuovo metodo di estensione per ottenere un "qualunque numero intero" casuale valore, che ritengo utile.

Nota che la distribuzione dei decimali qui non è 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);
}

Altri suggerimenti

Normalmente ti aspetteresti da un generatore di numeri casuali che non solo ha generato numeri casuali, ma che i numeri sono stati generati in modo uniforme in modo casuale.

Esistono due definizioni di uniformemente casuale: discreto uniformemente casuale e < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer"> continuo uniformemente casuale .

Discretamente uniformemente casuale ha senso per un generatore di numeri casuali che ha un numero finito di diversi possibili risultati. Ad esempio, generare un numero intero compreso tra 1 e 10. Ti aspetteresti quindi che la probabilità di ottenere 4 sia la stessa di ottenere 7.

In modo uniformemente casuale ha senso quando il generatore di numeri casuali genera numeri in un intervallo. Ad esempio un generatore che genera un numero reale compreso tra 0 e 1. Ci si aspetterebbe quindi che la probabilità di ottenere un numero compreso tra 0 e 0,5 sia uguale a ottenere un numero compreso tra 0,5 e 1.

Quando un generatore di numeri casuali genera numeri in virgola mobile (che è fondamentalmente ciò che è un System.Decimal - è solo in virgola mobile quale base 10), è discutibile quale sia la definizione corretta di uniformemente casuale:

Da un lato, poiché il numero in virgola mobile è rappresentato da un numero fisso di bit in un computer, è ovvio che ci sono un numero finito di possibili esiti. Quindi si potrebbe sostenere che la distribuzione corretta è una distribuzione continua discreta con ogni numero rappresentabile che ha la stessa probabilità. Questo è fondamentalmente ciò che Jon Skeet's e L'implementazione di John Leidegren sì.

D'altra parte, si potrebbe sostenere che, poiché si suppone che un numero in virgola mobile sia un'approssimazione a un numero reale, sarebbe meglio cercare di approssimare il comportamento di un generatore di numeri casuali continuo, anche se sono l'RNG effettivo è in realtà discreto. Questo è il comportamento che ottieni da Random.NextDouble (), dove - anche se ci sono approssimativamente tanti numeri rappresentabili nell'intervallo 0,00001-0,00002 quanti sono nell'intervallo 0,8-0,9, hai mille volte più probabilità di ottenere un numero nel secondo intervallo, come previsto.

Quindi una corretta implementazione di un Random.NextDecimal () dovrebbe probabilmente essere distribuita in modo uniforme in modo continuo.

Ecco una semplice variante della risposta di Jon Skeet che è distribuita uniformemente tra 0 e 1 (riutilizzo il suo metodo di estensione NextInt32 ()):

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

Potresti anche discutere su come ottenere una distribuzione uniforme su tutta la gamma di decimali. Esiste probabilmente un modo più semplice per farlo, ma questa leggera modifica di Risposta di John Leidegren dovrebbe produrre una distribuzione relativamente 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);
}

Fondamentalmente, ci assicuriamo che i valori di scala siano scelti proporzionalmente alla dimensione dell'intervallo corrispondente.

Ciò significa che dovremmo ottenere una scala dello 0 90% delle volte - poiché tale intervallo contiene il 90% della gamma possibile - una scala dell'1 9% delle volte, ecc.

Ci sono ancora alcuni problemi con l'implementazione, poiché tiene conto del fatto che alcuni numeri hanno rappresentazioni multiple - ma dovrebbe essere molto più vicino a una distribuzione uniforme rispetto alle altre implementazioni.

Ecco Decimale casuale con l'implementazione di Range che funziona bene per me.

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

So che questa è una vecchia domanda, ma il problema di distribuzione descritto da Rasmus Faber continuava a darmi fastidio, così mi sono inventato il seguente. Non ho approfondito la implementazione NextInt32 fornita da Jon Skeet e suppongo (sperando) che abbia la stessa distribuzione di 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);
}

È anche, attraverso il potere di cose semplici, fare:

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

Sono rimasto perplesso per un po '. Questo è il meglio che ho potuto inventare:

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

Modifica: come notato nei commenti lo, mid e hi non possono mai contenere int.MaxValue, quindi la gamma completa di decimali non è possibile.

ecco ... usa la libreria crypt per generare un paio di byte casuali, quindi li converte in un valore decimale ... vedi MSDN per il costruttore decimale

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

modificato per utilizzare un diverso costruttore decimale per fornire una migliore gamma di numeri

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

Controlla il seguente link per implementazioni pronte che dovrebbero aiutare:

MathNet.Numerics, Numeri casuali e distribuzioni di probabilità

Le estese distribuzioni sono particolarmente interessanti, basate sui generatori di numeri casuali (MersenneTwister, ecc.) direttamente derivati ??da System.Random, che forniscono tutti metodi di estensione pratici (ad esempio NextFullRangeInt32, NextFullRangeInt64, NextDecimal, ecc.). Ovviamente puoi semplicemente utilizzare SystemRandomSource predefinito, che è semplicemente System.Random abbellito con i metodi di estensione.

Oh, e puoi creare le tue istanze RNG come thread-safe se ne hai bisogno.

Davvero molto utile!

Questa è una vecchia domanda, ma per coloro che la stanno solo leggendo, perché reinventare la ruota?

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

Ad essere sincero, non credo che il formato interno del decimale C # funzioni come molte persone pensano. Per questo motivo, almeno alcune delle soluzioni qui presentate sono probabilmente non valide o potrebbero non funzionare in modo coerente. Considera i seguenti 2 numeri e come sono memorizzati nel formato decimale:

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

e

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

Prendi nota di come la scala è diversa, ma entrambi i valori sono quasi uguali, cioè sono entrambi meno di 1 solo di una piccola frazione. Sembra che sia la scala e il numero di cifre che hanno una relazione diretta. A meno che non mi manchi qualcosa, questo dovrebbe gettare una chiave inglese nella maggior parte dei codici che manomette la parte intera a 96 bit di un decimale ma lascia invariata la scala.

Nell'esperimento ho scoperto che il numero 0.999999999999999999999999999999m, che ha 28 nove, ha il numero massimo di nove possibili prima che il decimale venga arrotondato a 1,0 m.

Ulteriori esperimenti hanno dimostrato che il codice seguente imposta la variabile "Dec" al valore 0.999999999999999999999999999999m:

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

È da questa scoperta che mi sono venute in mente le estensioni della classe Casuale che si possono vedere nel codice qui sotto. Credo che questo codice sia perfettamente funzionante e in buone condizioni, ma sarei felice che altri occhi lo controllino per errori. Non sono uno statistico, quindi non posso dire se questo codice produce una distribuzione davvero uniforme dei decimali, ma se dovessi indovinare direi che fallisce la perfezione ma si avvicina estremamente (come in 1 chiamata su 51 trilioni che favoriscono un un certo intervallo di numeri).

La prima funzione NextDecimal () dovrebbe produrre valori uguali o superiori a 0,0 me inferiori a 1,0 m. L'istruzione do / while impedisce a RandH e RandL di superare il valore 0.9999999999999999d eseguendo il loop fino a quando non sono inferiori a tale valore. Credo che le probabilità che questo ciclo si ripeta mai sono 1 su 51 trilioni (l'enfasi sulla parola credi, non mi fido della mia matematica). Questo a sua volta dovrebbe impedire alle funzioni di arrotondare mai il valore di ritorno fino a 1,0 m.

La seconda funzione NextDecimal () dovrebbe funzionare allo stesso modo della funzione Random.Next (), solo con valori decimali anziché numeri interi. In realtà non sto usando questa seconda funzione NextDecimal () e non l'ho testata. È abbastanza semplice, quindi penso di averlo fatto bene, ma ancora una volta non l'ho testato, quindi dovrai assicurarti che funzioni correttamente prima di fare affidamento su di esso.

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

Volevo generare " random " decimali fino a 9 cifre decimali. Il mio approccio era generare un doppio e dividerlo per i decimali.

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

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

il " randomInt " è il numero prima della cifra decimale, puoi semplicemente inserire 0. Per ridurre i punti decimali è sufficiente rimuovere "9" in modo casuale e "0" in divisione

Dal momento che la domanda del PO è molto avvolgente e vuole solo un System.Decimal casuale senza alcuna restrizione, di seguito è una soluzione molto semplice che ha funzionato per me.

Non ero interessato a nessun tipo di uniformità o precisione dei numeri generati, quindi le risposte qui sono probabilmente migliori se si hanno alcune restrizioni, ma questa funziona bene in casi semplici.

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

Nel mio caso specifico, stavo cercando un decimale casuale da usare come stringa di denaro, quindi la mia soluzione completa era:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top