Frage

Wie kann ich eine zufällige System.Decimal bekommen? System.Random es nicht direkt unterstützen.

War es hilfreich?

Lösung

EDIT: Entfernt alte Version

Dies ist ähnlich Daniels Version, wird aber die gesamte Bandbreite geben. Es führt auch ein neues Erweiterungsmethode eine zufälligen „eine ganze Zahl“ Wert zu erhalten, die ich denke, ein.

Beachten Sie, dass die Verteilung der Dezimalstellen hier ist nicht einheitlich .

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

Andere Tipps

Sie würden normalerweise von einem Zufallszahlengenerator erwarten, dass es nicht nur Zufallszahl erzeugt wird, sondern dass die Zahlen gleichmäßig zufällig generiert wurden.

Es gibt zwei Definitionen von gleichmäßig zufällig: diskreten gleichmäßig zufällig und < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer"> kontinuierlich gleichmäßig zufällig .

Diskret gleichmäßig zufällig macht Sinn für einen Zufallszahlengenerator, der eine endliche Anzahl von verschiedenen möglichen Ergebnissen hat. Zum Beispiel eine ganze Zahl zwischen 1 und 10 zu erzeugen würden Sie dann erwarten, dass die Wahrscheinlichkeit von 4 immer das gleiche ist wie 7 zu bekommen.

Continuously gleichmäßig zufällig macht Sinn, wenn der Zufallszahlengenerator Zahlen in einem Bereich erzeugt. Zum Beispiel eines Generator, der eine reelle Zahl zwischen 0 und 1 erzeugt würden Sie dann erwarten, dass die Wahrscheinlichkeit für eine Zahl zwischen 0 des Erhalten und 0,5 ist das gleiche wie eine Zahl zwischen 0,5 und 1 zu bekommen.

Wenn ein Zufallszahlengenerator Gleitkommazahlen erzeugt (die im Grunde genommen ist, was ein System.Decimal ist - einfach ist Gleitkommazahlen, die 10-Base), ist es fraglich ist, was die korrekte Definition von gleichmäßig zufällig ist:

Einerseits, da die Gleitkommazahl durch eine feste Anzahl von Bits in einem Computer dargestellt wird, ist es offensichtlich, dass es eine endliche Anzahl von möglichen Ergebnissen ist. So könnte man argumentieren, dass die richtige Verteilung ist eine diskrete kontinuierliche Verteilung mit jeder darstellbaren Zahl die gleiche Wahrscheinlichkeit hat. Das ist im Grunde, was Jon Skeet und John Leidegren der Implementierung der Fall ist.

Auf der anderen Seite, könnte man argumentieren, dass da eine Gleitkommazahl soll eine Annäherung an eine reelle Zahl sein, würden wir uns, indem Sie versuchen, das Verhalten eines kontinuierlichen Zufallszahlengenerator besser dran zu nähern - obwohl sind tatsächliche RNG ist eigentlich diskret. Dies ist das Verhalten, das Sie von Random.NextDouble get (), wo - auch wenn es etwa so viele darstellbaren Zahlen liegen im Bereich von 0,00001 bis 0,00002, da es im Bereich von 0,8 bis 0,9 ist, Sie sind tausendmal eher ein erhalten Nummer im zweiten Bereich -. wie man erwarten würde

So eine ordnungsgemäße Durchführung eines Random.NextDecimal () wahrscheinlich kontinuierlich gleichmäßig verteilt werden soll.

Hier ist eine einfache Variation von Jon Skeet Antwort, die zwischen 0 und 1 (I Wiederverwendung seiner NextInt32 () Erweiterungsmethode) gleichmäßig verteilt ist:

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

Sie können auch darüber diskutieren, wie eine gleichmäßige Verteilung über den gesamten Bereich der Nachkommastellen zu erhalten. Es ist wahrscheinlich ein einfacher Weg, dies zu tun, aber diese leichte Modifikation von John Leidegren Antwort sollte eine relativ gleichmäßige Verteilung erzeugen:

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

Im Grunde stellen wir sicher, dass die Werte der Skala proportional zur Größe des entsprechenden Bereichs gewählt werden.

Das bedeutet, dass wir eine Skala von 0 90% der Zeit bekommen sollten - da dieser Bereich 90% des möglichen Bereichs enthält -. Eine Skala von 1 bis 9% der Zeit, etc.

Es gibt noch einige Probleme bei der Umsetzung, da sie nicht berücksichtigt, dass einige Zahlen mehr Darstellungen haben -. Aber es sollte als die anderen Implementierungen zu einer gleichmäßigen Verteilung viel näher sein

Hier ist Dezimal zufällig mit Bereich Implementierung, die für mich gut funktioniert.

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

Ich weiß, dass dies eine alte Frage, aber die Verteilung Ausgabe Rasmus Faber beschrieben gehalten stört mich so kam ich mit folgende. Ich habe nicht in der Tiefe bei der NextInt32 Implementierung von Jon Skeet zur Verfügung gestellt sehe und gehe davon aus (in der Hoffnung) es hat die gleiche Verteilung wie 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);
}

Es ist auch durch die Kraft der einfachen Dinge zu tun:

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

verwirrte ich mit diesem für ein bisschen. Dies ist das Beste, was ich tun konnte,:

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:. Wie in den Kommentaren lo, Mitte zur Kenntnis genommen und hallo kann nie int.MaxValue enthalten, so dass die komplette Palette von Dezimalzahlen nicht möglich ist,

hier geht ... verwendet die Krypta Bibliothek ein paar zufällige Bytes zu erzeugen, dann convertes sie in einen Dezimalwert ... siehe MSDN für das Dezimalsystem Konstruktor

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

überarbeitete einen anderen Dezimal-Konstruktor zu verwenden, um eine bessere Auswahl von Zahlen zu geben

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

Überprüfen Sie den folgenden Link für fertige Implementierungen aus, die dazu beitragen sollte:

MathNet.Numerics, Zufallszahlen und Wahrscheinlichkeitsverteilungen

Die umfangreichen Verteilungen sind besonders von Interesse, gebaut auf der Oberseite der Zufallszahlengeneratoren (Mersenne-Twister, etc.) direkt aus System.Random abgeleitet, alles Bereitstellung praktisch Erweiterungsmethoden (z NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). Sie können natürlich, verwenden Sie einfach die Standard SystemRandomSource, die einfach System.Random mit den Erweiterungsmethoden verschönert ist.

Oh, und Sie können Ihre RNG-Instanzen als Thread-sicher erstellen, wenn Sie es brauchen.

Sehr praktisch in der Tat!

Dies ist eine alte Frage, aber für diejenigen, die sie gerade lesen, warum neu erfinden das Rad?

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

Um ehrlich zu sein, ich glaube nicht, das interne Format der C # dezimal die Art und Weise viele Leute denken, funktioniert. Aus diesem Grunde zumindest einige der Lösungen, die hier vorgestellt werden sind möglicherweise ungültig oder nicht konsequent arbeiten. Betrachten Sie die folgenden zwei Zahlen und wie sie im Dezimalsystem-Format gespeichert werden:

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

und

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

Beachten Sie besonders, wie die Skala anders ist, aber beide Werte sind nahezu gleich, das heißt, sie sind beide weniger als 1 durch nur einen winzigen Bruchteil. Es scheint, dass es die Größe und die Anzahl der Stellen ist, die eine direkte Beziehung hat. Es sei denn, ich etwas fehle, sollte dies einen Code, um einen Schraubenschlüssel in den meisten wirft, die mit dem 96-Bit-Integer-Teil einer Dezimalzahl Stampfer lassen aber die Skala unverändert.

In Experimentieren fand ich, dass die Zahl 0.9999999999999999999999999999m, die 28 Neunen hat, hat die maximale Anzahl von Neunen möglich vor dem Komma zu 1,0m aufrunden wird.

Weitere Experimente bewiesen, der folgende Code die Variable „Dezember“ auf den Wert 0.9999999999999999999999999999m setzt:

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

Es ist von dieser Entdeckung, dass ich mit den Erweiterungen der Klasse Random aufkommen, die in dem unten stehenden Code zu sehen sind. Ich glaube, dass dieser Code voll funktionsfähig ist und in einem guten Zustand, aber würde mich freuen, für andere Augen für Fehler überprüft werden. Ich bin kein Statistiker, so kann ich nicht sagen, wenn dieser Code eine wirklich gleichmäßige Verteilung der Dezimalstellen produziert, aber wenn ich werde ich raten müsste sagen, dass es Vollkommenheit versagt aber sehr nahe kommt (wie in 1 Aufruf von 51 Billionen begünstigen eine bestimmter Zahlenbereich).

Die erste NextDecimal () Funktion sollte Werte gleich oder größer als 0,0 Mio. und weniger als 1,0 M erzeugen. Die do / while-Anweisung verhindert RandH und Randl übersteigt den Wert 0.99999999999999d durch Looping, bis sie unter diesem Wert liegen. Ich glaube, die Chancen dieser Schleife immer sind 1 in 51 Billionen Wiederholung (Betonung auf dem Wort glauben, ich weiß nicht meiner Mathe vertrauen). Dies wiederum sollte die Funktionen verhindert, dass jemals bis zu 1,0m den Rückgabewert gerundet wird.

Die zweite NextDecimal () Funktion sollte das gleiche wie die Random.Next () Funktion funktioniert nur mit Dezimalzahlen anstelle von ganzen Zahlen. Ich habe eigentlich nicht gewesen, diese zweite NextDecimal () Funktion und habe es nicht getestet. Es ist ziemlich einfach, so denke ich, ich habe es richtig, aber wieder, ich habe es nicht getestet -. So werden Sie sicherstellen wollen, dass es ordnungsgemäß funktioniert, bevor Sie es unter Berufung

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

Ich wollte „random“ Dezimalstellen bis zu 9 Dezimalstellen erzeugen. Mein Ansatz war nur ein Doppel zu generieren und teilen sie für die Dezimalstellen.

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

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

die „randomInt“ ist die Zahl vor dem Komma, könnten Sie setzen nur 0. Zur Reduzierung der Dezimalstellen einfach "9" s in zufälligen entfernen und "0" in Dividieren

Da die OP Frage sehr umarmend ist und will nur eine zufällige System.Decimal ohne jede Einschränkung, unten ist eine sehr einfache Lösung, die für mich gearbeitet.

war ich nicht mit jeder Art von Uniformität oder Genauigkeit der generierten Zahlen betrifft, so andere Antworten hier wahrscheinlich besser sind, wenn Sie einige Einschränkungen, aber diese funktioniert in einfachen Fällen.

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

In meinem speziellen Fall, ich war für eine zufällige Dezimalzahl suchen als Geld Zeichenfolge zu verwenden, so dass meine komplette Lösung war:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top