Pregunta

¿Cómo puedo obtener un System.Decimal aleatorio? System.Random no lo admite directamente.

¿Fue útil?

Solución

EDITAR: Se eliminó la versión anterior

Esto es similar a la versión de Daniel, pero le dará el rango completo. También introduce un nuevo método de extensión para obtener un " cualquier entero " valor, que creo que es útil.

Tenga en cuenta que la distribución de decimales aquí no es 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);
}

Otros consejos

Por lo general, se esperaría de un generador de números aleatorios que no solo generara números aleatorios, sino que los números se generaran de manera uniforme y aleatoria.

Hay dos definiciones de uniformemente aleatorio: discreto uniformemente aleatorio y < a href = "http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)" rel = "nofollow noreferrer"> continuo aleatoriamente uniforme .

Discretamente uniformemente aleatorio tiene sentido para un generador de números aleatorios que tiene un número finito de diferentes resultados posibles. Por ejemplo, generar un número entero entre 1 y 10. Entonces esperaría que la probabilidad de obtener 4 sea la misma que obtener 7.

El azar aleatorio de manera continua tiene sentido cuando el generador de números aleatorios genera números en un rango. Por ejemplo, un generador que genera un número real entre 0 y 1. Entonces esperaría que la probabilidad de obtener un número entre 0 y 0.5 sea la misma que obtener un número entre 0.5 y 1.

Cuando un generador de números aleatorios genera números de punto flotante (que es básicamente lo que es un System.Decimal - es solo punto flotante en el que se basa 10), es discutible cuál es la definición correcta de uniformemente aleatorio:

Por un lado, dado que el número de punto flotante está representado por un número fijo de bits en una computadora, es obvio que hay un número finito de resultados posibles. Por lo tanto, se podría argumentar que la distribución adecuada es una distribución continua discreta con cada número representable con la misma probabilidad. Eso es básicamente lo que Jon Skeet's y La implementación de John Leidegren sí.

Por otra parte, se podría argumentar que dado que se supone que un número de punto flotante es una aproximación a un número real, estaríamos mejor si tratáramos de aproximar el comportamiento de un generador de números aleatorios continuo, aunque El RNG real es en realidad discreto. Este es el comportamiento que obtiene de Random.NextDouble (), donde, aunque hay aproximadamente tantos números representables en el rango de 0.00001-0.00002 como en el rango de 0.8-0.9, es mil veces más probable que obtenga un número en el segundo rango, como es de esperar.

Por lo tanto, una implementación adecuada de un Random.NextDecimal () probablemente debería distribuirse de manera continua y uniforme.

Aquí hay una variación simple de la respuesta de Jon Skeet que se distribuye uniformemente entre 0 y 1 (reutilizo su método de extensión NextInt32 ()):

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

También puedes discutir cómo obtener una distribución uniforme en todo el rango de decimales. Probablemente haya una forma más fácil de hacer esto, pero esta ligera modificación de La respuesta de John Leidegren debería producir una distribución 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);
}

Básicamente, nos aseguramos de que los valores de escala se elijan proporcionalmente al tamaño del rango correspondiente.

Eso significa que deberíamos obtener una escala del 0 90% del tiempo, ya que ese rango contiene el 90% del rango posible, una escala del 1 9% del tiempo, etc.

Todavía hay algunos problemas con la implementación, ya que toma en cuenta que algunos números tienen representaciones múltiples, pero deberían estar mucho más cerca de una distribución uniforme que las otras implementaciones.

Aquí hay decimal aleatorio con la implementación del rango que funciona bien para mí.

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

Sé que esta es una pregunta antigua, pero el problema de distribución que describió Rasmus Faber siguió molestándome, así que se me ocurrió el seguimiento. No he analizado en profundidad la implementación de NextInt32 proporcionada por Jon Skeet y supongo que (espero) que tenga la misma distribución 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);
}

También es, a través del poder de las cosas fáciles, hacer:

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

Me desconcerté con esto por un momento. Esto es lo mejor que pude encontrar:

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

Editar: Como se indica en los comentarios, media y alta nunca pueden contener int.MaxValue, por lo que no es posible el rango completo de decimales.

aquí tienes ... usa la biblioteca crypt para generar un par de bytes aleatorios, luego los convierte a un valor decimal ... mira MSDN para el constructor decimal

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

revisado para usar un constructor decimal diferente para dar un mejor rango de números

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

Revise el siguiente enlace para ver las implementaciones listas para usar que deberían ayudar:

MathNet.Numerics, números aleatorios y distribuciones de probabilidad

Las distribuciones extensas son de especial interés, creadas sobre los generadores de números aleatorios (MersenneTwister, etc.) directamente derivados de System.Random, que ofrecen métodos prácticos de extensión (por ejemplo, NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). Puede, por supuesto, simplemente usar el SystemRandomSource predeterminado, que es simplemente System.Random embellecido con los métodos de extensión.

Ah, y puedes crear tus instancias RNG como un hilo seguro si lo necesitas.

¡Muy útil por cierto!

Esta es una pregunta antigua, pero para aquellos que solo la están leyendo, ¿por qué reinventar la rueda?

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

Para ser honesto, no creo que el formato interno del decimal C # funcione de la manera en que muchos piensan. Por esta razón, al menos algunas de las soluciones presentadas aquí posiblemente no sean válidas o no funcionen de manera consistente. Considere los siguientes 2 números y cómo se almacenan en el formato decimal:

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

y

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

Tome nota especial de cómo la escala es diferente, pero ambos valores son casi iguales, es decir, ambos son menos de 1 por solo una pequeña fracción. Parece que es la escala y el número de dígitos que tienen una relación directa. A menos que me esté faltando algo, esto debería generar una llave inglesa en la mayoría de los códigos que manipulan la parte entera de 96 bits de un decimal pero que no modifican la escala.

Al experimentar, descubrí que el número 0.9999999999999999999999999999999999m, que tiene 28 nueves, tiene el número máximo de nueves posibles antes de que el decimal se redondee hasta 1.0 m.

La experimentación adicional demostró que el siguiente código establece la variable " Dec " al valor 0.999999999999999999999999999999m:

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

Es a partir de este descubrimiento que se me ocurrieron las extensiones de la clase Random que se pueden ver en el siguiente código. Creo que este código es completamente funcional y está en buen estado de funcionamiento, pero me alegraría que otros ojos lo verifiquen en busca de errores. No soy un estadístico, por lo que no puedo decir si este código produce una distribución de decimales realmente uniforme, pero si tuviera que adivinar diría que falla la perfección pero que se acerca mucho (como en 1 de cada 51 trillones de llamadas a favor de un cierto rango de números).

La primera función NextDecimal () debe producir valores iguales o mayores a 0.0m y menores a 1.0m. La instrucción do / while evita que RandH y RandL excedan el valor 0.99999999999999d haciendo un bucle hasta que estén por debajo de ese valor. Creo que las probabilidades de que este ciclo se repita son de 1 en 51 billones (énfasis en la palabra creer, no confío en mis matemáticas). Esto, a su vez, debería evitar que las funciones redondeen el valor de retorno hasta 1,0 m.

La segunda función NextDecimal () debería funcionar igual que la función Random.Next (), solo con valores decimales en lugar de números enteros. De hecho, no he estado utilizando esta segunda función NextDecimal () y no la he probado. Es bastante simple, así que creo que lo tengo bien, pero, una vez más, no lo he probado, así que querrás asegurarte de que funciona correctamente antes de confiar en él.

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

Quería generar " aleatorio " Decimales hasta 9 decimales. Mi enfoque fue generar un doble y dividirlo por los decimales.

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

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

el " randomInt " es el número antes de la posición decimal, solo puede poner 0. Para reducir los puntos decimales, simplemente elimine las " 9 " s al azar y " 0 " s en la división

Dado que la pregunta OP es muy abarcativa y solo quiere un sistema aleatorio. Decimal sin ninguna restricción, a continuación es una solución muy simple que funcionó para mí.

No me preocupaba ningún tipo de uniformidad o precisión de los números generados, por lo que otras respuestas aquí probablemente sean mejores si tiene algunas restricciones, pero esta funciona bien en casos simples.

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

En mi caso específico, estaba buscando un decimal aleatorio para usar como una cadena de dinero, por lo que mi solución completa fue:

string value;
value = val = Math.Round(new decimal(rnd.NextDouble()) * 1000,2).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top