Generador de números aleatorios que solo genera un número aleatorio


766

Tengo la siguiente función:

//Function to get random number
public static int RandomNumber(int min, int max)
{
    Random random = new Random();
    return random.Next(min, max);
}

Como lo llamo:

byte[] mac = new byte[6];
for (int x = 0; x < 6; ++x)
    mac[x] = (byte)(Misc.RandomNumber((int)0xFFFF, (int)0xFFFFFF) % 256);

Si paso ese bucle con el depurador durante el tiempo de ejecución obtengo valores diferentes (que es lo que quiero). Sin embargo, si pongo un punto de interrupción dos líneas debajo de ese código, todos los miembros de la macmatriz tienen el mismo valor.

¿Por qué sucede eso?


21
usar new Random().Next((int)0xFFFF, (int)0xFFFFFF) % 256);no produce mejores números "aleatorios" que.Next(0, 256)
bohdan_trotsenko

1
Puede encontrar este paquete NuGet útil. Proporciona un Rand.Next(int, int)método estático que proporciona acceso estático a valores aleatorios sin bloquear o encontrarse con el problema de reutilización de semillas
ChaseMedallion

Respuestas:


1043

Cada vez que lo haces new Random()se inicializa usando el reloj. Esto significa que en un ciclo cerrado obtienes el mismo valor muchas veces. Debe mantener una única instancia aleatoria y seguir usando Siguiente en la misma instancia.

//Function to get a random number 
private static readonly Random random = new Random(); 
private static readonly object syncLock = new object(); 
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

Editar (ver comentarios): ¿por qué necesitamos un lockaquí?

Básicamente, Nextva a cambiar el estado interno de la Randominstancia. Si hacemos eso al mismo tiempo desde múltiples hilos, podría argumentar "acabamos de hacer que el resultado sea aún más aleatorio", pero lo que realmente estamos haciendo es potencialmente romper la implementación interna, y también podríamos comenzar a obtener los mismos números de diferentes hilos, lo que podría ser un problema, y ​​podría no serlo. Sin embargo, la garantía de lo que sucede internamente es el problema más importante; ya Randomque no garantiza la seguridad del hilo. Por lo tanto, hay dos enfoques válidos:

  • Sincronice para que no accedamos al mismo tiempo desde diferentes hilos
  • Use diferentes Randominstancias por hilo

Cualquiera puede estar bien; pero eliminar una sola instancia de varias personas que llaman al mismo tiempo solo es un problema.

El locklogra el primero (y más simple) de estos enfoques; Sin embargo, otro enfoque podría ser:

private static readonly ThreadLocal<Random> appRandom
     = new ThreadLocal<Random>(() => new Random());

esto es por subproceso, por lo que no necesita sincronizar.


19
Como regla general, todos los métodos estáticos deben ser seguros para subprocesos, ya que es difícil garantizar que varios subprocesos no lo llamen al mismo tiempo. Por lo general, no es necesario hacer que los métodos de instancia (es decir, no estáticos) sean seguros para subprocesos.
Marc Gravell

55
@Florin: no hay diferencia en cuanto a "basado en la pila" entre los dos. Los campos estáticos son igual de "estado externo" y se compartirán absolutamente entre las personas que llaman. Con las instancias, existe una buena posibilidad de que diferentes subprocesos tengan instancias diferentes (un patrón común). Con las estadísticas, se garantiza que todos compartan (sin incluir [ThreadStatic]).
Marc Gravell

2
@gdoron, ¿recibes un error? El "bloqueo" debería evitar que los hilos se tropiecen unos con otros aquí ...
Marc Gravell

66
@ Dan si el objeto nunca se expone públicamente: puedes hacerlo. El riesgo (muy teórico) es que algún otro hilo se está bloqueando de una manera que no esperaba.
Marc Gravell

3
@smiron Es muy probable que también estés usando el azar al azar fuera de un candado. El bloqueo no impide todo el acceso a lo que está bloqueando, solo se asegura de que dos instrucciones de bloqueo en la misma instancia no se ejecuten simultáneamente. Por lock (syncObject)lo tanto , solo ayudará si todas las random.Next() llamadas también están dentro lock (syncObject). Si el escenario que describe ocurre incluso con el lockuso correcto , también es muy probable que ocurra en un escenario de subproceso único (por ejemplo, Randomestá sutilmente roto).
Luaan

118

Para facilitar la reutilización en toda la aplicación, una clase estática puede ayudar.

public static class StaticRandom
{
    private static int seed;

    private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
        (() => new Random(Interlocked.Increment(ref seed)));

    static StaticRandom()
    {
        seed = Environment.TickCount;
    }

    public static Random Instance { get { return threadLocal.Value; } }
}

Puede usar y luego usar una instancia aleatoria estática con código como

StaticRandom.Instance.Next(1, 100);

62

La solución de Mark puede ser bastante costosa ya que necesita sincronizarse cada vez.

Podemos evitar la necesidad de sincronización utilizando el patrón de almacenamiento específico de subproceso:


public class RandomNumber : IRandomNumber
{
    private static readonly Random Global = new Random();
    [ThreadStatic] private static Random _local;

    public int Next(int max)
    {
        var localBuffer = _local;
        if (localBuffer == null) 
        {
            int seed;
            lock(Global) seed = Global.Next();
            localBuffer = new Random(seed);
            _local = localBuffer;
        }
        return localBuffer.Next(max);
    }
}

Mida las dos implementaciones y debería ver una diferencia significativa.


12
Las cerraduras son muy baratas cuando no están en disputa ... e incluso si estuviese en disputa, esperaría que el código "ahora haga algo con el número" reduzca el costo de la cerradura en los escenarios más interesantes.
Marc Gravell

44
De acuerdo, esto resuelve el problema de bloqueo, pero no es todavía una solución muy complicada para un problema trivial: que necesita escribir "dos" líneas de código para generar un número aleatorio en lugar de uno. ¿Realmente vale la pena ahorrar leyendo una simple línea de código?
EMP

44
+1 Usar una Randominstancia global adicional para obtener la semilla es una buena idea. Tenga en cuenta también que el código puede simplificarse aún más utilizando la ThreadLocal<T>clase introducida en .NET 4 (como Phil también escribió a continuación ).
Groo

40

Mi respuesta desde aquí :

Solo reiterando la solución correcta :

namespace mySpace
{
    public static class Util
    {
        private static rnd = new Random();
        public static int GetRandom()
        {
            return rnd.Next();
        }
    }
}

Entonces puedes llamar:

var i = Util.GetRandom();

todo a través de.

Si necesita estrictamente un método estático sin estado verdadero para generar números aleatorios, puede confiar en a Guid.

public static class Util
{
    public static int GetRandom()
    {
        return Guid.NewGuid().GetHashCode();
    }
}

Va a ser un poco más lento, pero puede ser mucho más aleatorio que Random.Next, al menos desde mi experiencia.

Pero no :

new Random(Guid.NewGuid().GetHashCode()).Next();

La creación innecesaria de objetos lo hará más lento, especialmente bajo un bucle.

Y nunca más :

new Random().Next();

No solo es más lento (dentro de un bucle), su aleatoriedad es ... bueno, no es realmente bueno según yo ...


10
No estoy de acuerdo con el caso Guid. La clase Random implementa una distribución uniforme. Que no es el caso en Guid. El objetivo principal es ser único, no distribuido uniformemente (y su implementación se basa la mayor parte del tiempo en alguna propiedad de hardware / máquina que es lo opuesto a ... aleatoriedad).
Askolein

44
Si no puede probar la uniformidad de la generación Guid, entonces está mal usarlo al azar (y el Hash estaría un paso más allá de la uniformidad). Del mismo modo, las colisiones no son un problema: la uniformidad de la colisión sí lo es. Con respecto a que la generación Guid ya no está en el hardware, voy a RTFM, mi mal (¿alguna referencia?)
Askolein

55
Hay dos interpretaciones de "Aleatorio": 1. falta de patrón o 2. falta de patrón siguiendo una evolución descrita por una distribución de probabilidad (2 incluidos en 1). Su ejemplo Guid es correcto en el caso 1, no en el caso 2. En contrario: la Randomclase coincide con el caso 2 (por lo tanto, el caso 1 también). Sólo se puede reemplazar el uso de Randompor su Guid+Hashsi está no en el caso 2. Caso 1 es probablemente suficiente para responder a la pregunta, y luego, su Guid+Hashfina obras. Pero no se dice claramente (ps: este uniforme )
Askolein

2
@Askolein Solo para algunos datos de prueba, ejecuto varios lotes de ambos Randomy a Guid.NewGuid().GetHashCode()través de Ent ( fourmilab.ch/random ) y ambos son igualmente aleatorios. new Random(Guid.NewGuid().GetHashCode())funciona igual de bien, al igual que el uso de un "maestro" sincronizado Randompara generar semillas para "hijos" Random. Por supuesto, depende de cómo su sistema genera Guías: para mi sistema, son bastante aleatorias, y en otras puede incluso ser criptoaleatorio. Entonces, Windows o MS SQL parece estar bien hoy en día. Sin embargo, Mono y / o móvil pueden ser diferentes.
Luaan

2
@EdB Como dije anteriormente en los comentarios, mientras que Guid (un gran número) está destinado a ser único, GetHashCodeel Guid en .NET se deriva de su representación de cadena. La salida es bastante aleatoria para mi gusto.
nawfal

27

Preferiría usar la siguiente clase para generar números aleatorios:

byte[] random;
System.Security.Cryptography.RNGCryptoServiceProvider prov = new System.Security.Cryptography.RNGCryptoServiceProvider();
prov.GetBytes(random);

30
No soy uno de los votantes negativos, pero tenga en cuenta que el PNRG estándar cumple una necesidad genuina, es decir, poder reproducir repetidamente una secuencia de una semilla conocida. A veces, el costo total de un verdadero RNG criptográfico es demasiado. Y a veces es necesario un Crypto RNG. Caballos para cursos, por así decirlo.
Marc Gravell

44
De acuerdo con la documentación, esta clase es segura para subprocesos, por lo que es algo a su favor.
Rob Church

¿Cuál es la probabilidad de que dos cadenas aleatorias sean una y la misma usando eso? Si la cadena tiene solo 3 caracteres, supongo que esto sucederá con alta probabilidad, pero ¿qué pasa si la longitud de 255 caracteres es posible tener la misma cadena aleatoria o se garantiza que esto no puede suceder desde el algoritmo?
Lyubomir Velchev

16

1) Como dijo Marc Gravell, intente usar UN generador aleatorio. Siempre es bueno agregar esto al constructor: System.Environment.TickCount.

2) Un consejo. Digamos que desea crear 100 objetos y suponga que cada uno de ellos debe tener su propio generador aleatorio (útil si calcula CARGAS de números aleatorios en un período de tiempo muy corto). Si haría esto en un bucle (generación de 100 objetos), podría hacerlo así (para asegurar una aleatoriedad total):

int inMyRandSeed;

for(int i=0;i<100;i++)
{
   inMyRandSeed = System.Environment.TickCount + i;
   .
   .
   .
   myNewObject = new MyNewObject(inMyRandSeed);  
   .
   .
   .
}

// Usage: Random m_rndGen = new Random(inMyRandSeed);

Salud.


3
Quitaría System.Environment.TickCount fuera del bucle. Si se activa mientras está iterando, tendrá dos elementos inicializados en la misma semilla. Otra opción sería combinar el tickcount e i de manera diferente (por ejemplo, System.Environment.TickCount << 8 + i)
Dolphin

Si lo entiendo correctamente: ¿quiere decir que podría suceder que "System.Environment.TickCount + i" pudiera generar el MISMO valor?
sabiland

EDITAR: Por supuesto, no es necesario tener TickCount dentro del bucle. Culpa mía :).
sabiland

2
El Random()constructor predeterminado llama de Random(Environment.TickCount)todos modos
Alsty

5

Cada vez que ejecutas

Random random = new Random (15);

No importa si lo ejecutas millones de veces, siempre usarás la misma semilla.

Si utiliza

Random random = new Random ();

Obtiene una secuencia de números aleatorios diferente, si un pirata informático adivina la semilla y su algoritmo está relacionado con la seguridad de su sistema, su algoritmo está roto. Yo ejecuto mult. En este constructor, la semilla se especifica mediante el reloj del sistema y, si se crean varias instancias en un período de tiempo muy corto (milisegundos), es posible que tengan la misma semilla.

Si necesita números aleatorios seguros, debe usar la clase

System.Security.Cryptography.RNGCryptoServiceProvider

public static int Next(int min, int max)
{
    if(min >= max)
    {
        throw new ArgumentException("Min value is greater or equals than Max value.");
    }
    byte[] intBytes = new byte[4];
    using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        rng.GetNonZeroBytes(intBytes);
    }
    return  min +  Math.Abs(BitConverter.ToInt32(intBytes, 0)) % (max - min + 1);
}

Uso:

int randomNumber = Next(1,100);

It does not matter if you execute it millions of times, you will always use the same seed. Eso no es cierto a menos que especifique la semilla usted mismo.
LarsTech

Arreglado. Gracias Exactamente como dices LarsTech, si siempre se especifica la misma semilla, siempre se generará la misma secuencia de números aleatorios. En mi respuesta, me refiero al constructor con parámetros si siempre usa la misma semilla. La clase Random genera solo números pseudoaleatorios. Si alguien descubre qué semilla ha utilizado en su algoritmo, puede comprometer la seguridad o la aleatoriedad de su algoritmo. Con la clase RNGCryptoServiceProvider, puede tener números aleatorios de forma segura. Ya lo corregí, muchas gracias por la corrección.
Joma

0

simplemente declare la variable de clase Random de esta manera:

    Random r = new Random();
    // ... Get three random numbers.
    //     Here you'll get numbers from 5 to 9
    Console.WriteLine(r.Next(5, 10));

si desea obtener un número aleatorio diferente cada vez de su lista, use

r.Next(StartPoint,EndPoint) //Here end point will not be included

Cada vez declarando Random r = new Random()una vez.


Cuando lo llama new Random(), usa el reloj del sistema, pero si llama a ese código completo dos veces seguidas antes de que cambie el reloj, obtendrá el mismo número aleatorio. Ese es el punto principal de las respuestas anteriores.
Salvaje

-1

Hay muchas soluciones, aquí una: si solo desea borrar las letras y el método recibe una longitud aleatoria y el resultado.

public String GenerateRandom(Random oRandom, int iLongitudPin)
{
    String sCharacters = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ123456789";
    int iLength = sCharacters.Length;
    char cCharacter;
    int iLongitudNuevaCadena = iLongitudPin; 
    String sRandomResult = "";
    for (int i = 0; i < iLongitudNuevaCadena; i++)
    {
        cCharacter = sCharacters[oRandom.Next(iLength)];
        sRandomResult += cCharacter.ToString();
    }
    return (sRandomResult);
}

El problema básico sigue siendo el mismo: está pasando una Randominstancia, pero aún espera que la persona que llama cree una instancia compartida. Si la persona que llama crea una nueva instancia cada vez y el código se ejecuta dos veces antes de que cambie el reloj, obtendrá el mismo número aleatorio. Entonces, esta respuesta aún hace suposiciones que podrían ser erróneas.
Salvaje

Además, el objetivo de tener un método para generar números aleatorios es la encapsulación - que el método que llama no tiene que preocuparse acerca de la aplicación, que sólo está interesado en obtener una copia de números aleatorios
salvaje
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.