Generando un decimal aleatorio en C #

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

EDITAR: Se eliminó la versión anterior

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

Tenga en cuenta que la distribución de decimales aquí no es uniforme .

 ///  /// Returns an Int32 with a random value across the entire range of /// possible values. ///  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); } 

Normalmente esperaría de un generador de números aleatorios que no solo generara números aleatorios, sino que los números se generaran aleatoriamente de manera uniforme.

Hay dos definiciones de uniformemente aleatorio: discreto uniformemente aleatorio y continuo uniformemente aleatorio .

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.

Continuamente uniformemente aleatorio 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 coma flotante (que es básicamente lo que es un System.Decimal – es simplemente floating-point que base 10), es discutible cuál es la definición correcta de uniformidad aleatoria:

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

Por otro lado, 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 intentando aproximar el comportamiento de un generador de números aleatorios continuo, aunque el RNG real sea realmente discreto. Este es el comportamiento que obtienes de Random.NextDouble (), donde – aunque hay aproximadamente tantos números representables en el rango 0.00001-0.00002 como hay en el rango 0.8-0.9, tienes mil veces más probabilidades de obtener un número en el segundo rango, como era de esperar.

Por lo tanto, una implementación adecuada de un Random.NextDecimal () probablemente se distribuya uniformemente de manera continua.

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 podría analizar cómo obtener una distribución uniforme en todo el rango de decimales. Probablemente haya una manera más fácil de hacerlo, 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 de 0 el 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 tiene en cuenta que algunos números tienen representaciones múltiples, pero debe ser mucho más cercano a una distribución uniforme que las otras implementaciones.

Aquí está Decimal al azar con implementación de 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 vieja pregunta, pero el problema de distribución que describió Rasmus Faber me seguía molestando, así que se me ocurrió lo siguiente. No he profundizado en la implementación de NextInt32 proporcionada por Jon Skeet y estoy asumiendo (esperando) 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, mediante el poder de cosas fáciles, hacer:

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

Me quedé desconcertado con esto por un momento. Esto es lo mejor que pude hacer:

 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 indicó en los comentarios, mid y hi 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 … ver 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); } 

Consulte el siguiente enlace para las implementaciones ya hechas que deberían ayudar:

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

Las extensas distribuciones son especialmente interesantes, construidas sobre los generadores de números aleatorios (MersenneTwister, etc.) derivados directamente de System.Random, que proporcionan métodos de extensión útiles (por ejemplo, NextFullRangeInt32, NextFullRangeInt64, NextDecimal, etc.). Por supuesto, puede usar SystemRandomSource predeterminado, que es simplemente System.Random embellecido con los métodos de extensión.

Ah, y puede crear sus instancias de RNG como hilo seguro si lo necesita.

Muy útil de hecho!

Esta es una vieja pregunta, 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 sincero, no creo que el formato interno del decimal C # funcione de la manera en que muchas personas piensan. Por esta razón, al menos algunas de las soluciones presentadas aquí posiblemente sean inválidas o no funcionen de manera consistente. Considere los siguientes 2 números y cómo se almacenan en 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 los mismos, es decir, ambos son menores que 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 falta algo, esto debería arrojar una llave inglesa en la mayoría de los códigos que alteran la parte entera de 96 bits de un decimal, pero no cambian la escala.

Al experimentar encontré que el número 0.9999999999999999999999999999m, que tiene 28 nueves, tiene el número máximo de nueves posibles antes de que el decimal se redondee hasta 1.0m.

La experimentación adicional demostró que el siguiente código establece la variable “Dec” con el 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 funciona bien, pero estaría contento de que otros ojos lo revisen en busca de errores. No soy un estadístico, así que no puedo decir si este código produce una distribución verdaderamente uniforme de decimales, pero si tuviera que adivinar, diría que falla la perfección, pero se acerca demasiado (como en 1 llamada de los 51 trillones a favor de una cierto rango de números).

La primera función NextDecimal () debe producir valores iguales o superiores a 0.0 my menores a 1.0 m. La instrucción do / while impide que RandH y RandL excedan el valor 0.99999999999999d mediante un bucle hasta que estén por debajo de ese valor. Creo que las probabilidades de que este bucle se repita son 1 en 51 billones (énfasis en la palabra creer, no confío en mis cálculos). 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 enteros. De hecho, no he estado usando esta segunda función NextDecimal () y no la he probado. Es bastante simple, así que creo que lo tengo bien, pero de nuevo, no lo he probado, así que querrás asegurarte de que esté funcionando 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 decimales “aleatorios” de hasta 9 decimales. Mi enfoque fue generar un doble y dividirlo para 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 anterior a la posición decimal, puede poner 0. Para reducir los puntos decimales simplemente elimine “9” s al azar y “0” s en la división

Dado que la pregunta OP es muy abarcativa y solo quiero un System.Decimal aleatorio sin ninguna restricción, a continuación se muestra 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);