2010-01-04 15 views
5

Nota: Por motivos de brevedad, lo siguiente no distinguirá entre aleatoriedad y pseudoaleatoriedad. También, en este contexto, constreñidos significa entre dado min y max valores)Generación de valores (pseudo) aleatorios de (U) Int64 y Decimal

La clase System.Random proporciona la generación aleatoria de números enteros, dobles y matrices de bytes. Usando Random.Next, uno puede generar fácilmente valores restringidos aleatorios de tipo Boolean, Char, (S) Byte, (U) Int16, (U) Int32. Usando Random.NextDouble(), uno puede generar de manera similar valores restringidos de tipos Doble e Individual (en lo que se refiere a mi comprensión de este tipo). Generación de cadenas aleatorias (de una longitud y un alfabeto determinados) hasalsobeentackledbefore.

Considere los tipos de datos primitivos restantes (excluyendo el objeto): decimal e (U) Int64. También se ha abordado su generación aleatoria (Decimal, (U)Int64 usando Random.NextBytes()), pero no cuando está restringida. El muestreo de rechazo (es decir, el bucle hasta que el valor generado es el rango deseado) podría usarse teóricamente, pero obviamente no es una solución práctica. La normalización NextDouble() no funcionará porque no tiene suficientes dígitos significativos.

En resumen, estoy pidiendo para la correcta aplicación de las siguientes funciones:

long NextLong(long min, long max) 
long NextDecimal(decimal min, decimal max) 

Tenga en cuenta que, dado que System.DateTime se basa en un ulong, la primera función permitiría la generación constreñido al azar de tales estructuras como bien (similar a here, solo en tics en lugar de minutos).

Respuesta

6

Supongamos que sabe cómo generar N bits aleatorios. Esto se hace fácilmente usando NextBytes o llamadas repetidas a Random.Next con los límites apropiados.

Para generar un largo/ulong en el rango correcto, determine qué tan grande es el rango y cuántos bits se necesitan para representarlo. Luego puede usar el muestreo de rechazo que en el peor rechaza la mitad de los valores generados (por ejemplo, si desea un valor en el rango [0, 128], lo que significa que generará [0, 255] varias veces). Si desea un rango basado en cero, simplemente resuelva el tamaño del rango, genere un valor aleatorio en [0, tamaño) y luego agregue la base.

Generar un decimal aleatorio es significativamente más difícil, creo, aparte de cualquier otra cosa, tendría que especificar la distribución que deseaba.

+0

¡Gracias por las pautas! +1 –

+0

¿De verdad es tan difícil? Me debe estar perdiendo algo, porque mi primer pensamiento fue utilizar 'Random.NextBytes' y usar operaciones bit a bit para aplicarlos a los bits decimales apropiados para obtener un valor entre 0 y 1. –

9

Esto debería hacerlo. Para decimal, utilicé el enfoque inicial de Jon Skeet para generar decimal aleatorios (sin restricciones). Para long proporcioné un método para producir longs aleatorios no negativos que luego se usa para crear el valor a en el rango aleatorio.

Tenga en cuenta que para decimal la distribución resultante no es una distribución uniforme en [minValue, maxValue]. Simplemente es uniforme en todas las representaciones de bits de decimales que se encuentran en el rango [minValue, maxValue]. No veo una manera fácil de evitar esto sin usar el muestreo de rechazo.

Para long la distribución resultante es uniforme en [minValue, maxValue).

static class RandomExtensions { 
    static int NextInt32(this Random rg) { 
     unchecked { 
      int firstBits = rg.Next(0, 1 << 4) << 28; 
      int lastBits = rg.Next(0, 1 << 28); 
      return firstBits | lastBits; 
     } 
    } 

    public static decimal NextDecimal(this Random rg) { 
     bool sign = rg.Next(2) == 1; 
     return rg.NextDecimal(sign); 
    } 

    static decimal NextDecimal(this Random rg, bool sign) { 
     byte scale = (byte)rg.Next(29); 
     return new decimal(rg.NextInt32(), 
          rg.NextInt32(), 
          rg.NextInt32(), 
          sign, 
          scale); 
    } 

    static decimal NextNonNegativeDecimal(this Random rg) { 
     return rg.NextDecimal(false); 
    } 

    public static decimal NextDecimal(this Random rg, decimal maxValue) { 
     return (rg.NextNonNegativeDecimal()/Decimal.MaxValue) * maxValue; ; 
    } 

    public static decimal NextDecimal(this Random rg, decimal minValue, decimal maxValue) { 
     if (minValue >= maxValue) { 
      throw new InvalidOperationException(); 
     } 
     decimal range = maxValue - minValue; 
     return rg.NextDecimal(range) + minValue; 
    } 

    static long NextNonNegativeLong(this Random rg) { 
     byte[] bytes = new byte[sizeof(long)]; 
     rg.NextBytes(bytes); 
     // strip out the sign bit 
     bytes[7] = (byte)(bytes[7] & 0x7f); 
     return BitConverter.ToInt64(bytes, 0); 
    } 

    public static long NextLong(this Random rg, long maxValue) { 
     return (long)((rg.NextNonNegativeLong()/(double)Int64.MaxValue) * maxValue); 
    } 

    public static long NextLong(this Random rg, long minValue, long maxValue) { 
     if (minValue >= maxValue) { 
      throw new InvalidOperationException(); 
     } 
     long range = maxValue - minValue; 
     return rg.NextLong(range) + minValue; 
    } 
} 
+0

Gracias, este es un gran fragmento (+1) - un par de preguntas: 1. ¿No perdería información en rg.NextNonNegativeLong()/(double) Int64.MaxValue? El elenco pierde precisión y la división puede no ser totalmente representable. ¿No afectarían estos factores la uniformidad y las propiedades? Lo mismo con respecto a NextNonNegativeDecimal()/Decimal.MaxValue 2. En NextInt32, ¿hay alguna razón por la cual se seleccionaron 4 y 28? dos números positivos que sumen 32 (por ejemplo, 16 + 16) deberían funcionar, ¿no? Gracias! –

+1

Este código no puede proporcionar un largo firmado. Llamar a '.NextLong (long.MinValue, long.MaxValue)' siempre devuelve '-9223372036854775808'. –

+0

NextDecimal (decimal.MinValue, 5) lanzará OverflowException – Danil

0

Con base en el método de Jon Skeet, aquí está mi puñalada en ella:

public static long NextLong(this Random rnd, long min, long max) 
{ 
    if (max <= min) 
    { 
     throw new Exception("Min must be less than max."); 
    } 

    long dif = max - min; 

    var bytes = new byte[8]; 
    rnd.NextBytes(bytes); 
    bytes[7] &= 0x7f; //strip sign bit 

    long posNum = BitConverter.ToInt64(bytes, 0); 
    while (posNum > dif) 
    { 
     posNum >>= 1; 
    } 

    return min + posNum; 
} 

Avísame si hay errores.

+0

Reducir un valor muy grande dividiéndolo por una potencia de 2 reduce el número de forma predecible. En su lugar, sería mejor eliminar bits MÁS importantes, no menos. Pruebe el cambio de bits de una máscara a la derecha hasta que el valor Y de la máscara en el bit esté dentro del rango. Obtienes una distribución mucho mejor de los valores aleatorios. – KeithS

-2
long posNum = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); 


use this instead of NextBytes 
+0

-1. Los GUID no son completamente aleatorios. No deberían usarse en lugar de un generador de números pseudoaleatorio. –

0

Vine aquí buscando una manera de generar valores de 64 bits dentro de un rango arbitrario. Las otras respuestas no generaron un número aleatorio cuando se les dieron ciertos rangos (por ejemplo long.MinValue to long.MaxValue). Aquí está mi versión que parece resolver el problema:

public static long NextInt64(this Random random, long minValue, long maxValue) 
{ 
    Contract.Requires(random != null); 
    Contract.Requires(minValue <= maxValue); 
    Contract.Ensures(Contract.Result<long>() >= minValue && 
        Contract.Result<long>() < maxValue); 

    return (long)(minValue + (random.NextUInt64() % ((decimal)maxValue - minValue))); 
} 

Se utiliza los siguientes métodos de extensión:

public static ulong NextUInt64(this Random random) 
{ 
    Contract.Requires(random != null); 

    return BitConverter.ToUInt64(random.NextBytes(8), 0); 
} 

public static byte[] NextBytes(this Random random, int byteCount) 
{ 
    Contract.Requires(random != null); 
    Contract.Requires(byteCount > 0); 
    Contract.Ensures(Contract.Result<byte[]>() != null && 
        Contract.Result<byte[]>().Length == byteCount); 

    var buffer = new byte[byteCount]; 
    random.NextBytes(buffer); 
    return buffer; 
} 

la distribución no es perfectamente incluso cuando el tamaño del rango solicitado no es un divisor limpia de 2^64, pero al menos proporciona un número aleatorio dentro del rango de solicitud para cualquier rango dado.

Cuestiones relacionadas