2010-01-04 3 views
5

Nota: Per brevità, quanto segue non discerne tra casualità e pseudo-casualità. Inoltre, in questo contesto, significa vincolatatra proposta minimo e massimo valori)Generazione di valori vincolati casuali (pseudo) di (U) Int64 e Decimale

La classe System.Random fornisce generazione casuale di numeri interi, doppie e campi di byte. Uso casuale. Successivamente, si possono facilmente generare valori vincolati casuali di tipo Booleano, Char, (S) Byte, (U) Int16, (U) Int32. Usando Random.NextDouble(), si possono similmente generare valori vincolati di tipi Double e Single (per quanto riguarda la mia comprensione di questo tipo). Generazione casuale di stringhe (di una determinata lunghezza e alfabeto) hasalsobeentackledbefore.

Considerare i restanti tipi di dati primitivi (escluso Oggetto): Decimale e (U) Int64. Anche la loro generazione casuale è stata affrontata (Decimal, (U)Int64 usando Random.NextBytes()), ma non quando vincolata. Il campionamento di rigetto (cioè il looping fino a quando il valore generato è l'intervallo desiderato) potrebbe teoricamente essere usato, ma ovviamente non è una soluzione pratica. La normalizzazione NextDouble() non funziona perché non ha cifre significative.

Insomma, chiedo per la corretta attuazione delle seguenti funzioni:

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

Si noti che, dal momento che System.DateTime si basa su un ulong, la prima funzione permetterebbe di generazione casuale vincolata di tali struct come bene (simile a here, solo in zecche anziché minuti).

risposta

6

Supponiamo che tu sappia come generare N bit casuali. Questo è abbastanza facile o utilizzando NextBytes o chiamate ripetute a Random.Next con limiti appropriati.

Per generare un intervallo lungo/lungo nell'intervallo di destra, calcolare la larghezza dell'intervallo e il numero di bit necessario per rappresentarlo. È quindi possibile utilizzare il campionamento di rifiuto che nel peggiore dei casi sarà il a rifiutare la metà dei valori generati (ad esempio se si desidera un valore nell'intervallo [0, 128], il che significa che genererà [0, 255] più volte). Se si desidera un intervallo basato su un valore diverso da zero, calcolare la dimensione dell'intervallo, generare un valore casuale in [0, dimensione] e quindi aggiungere la base.

Generare un decimale casuale è significativamente più difficile, credo - oltre a qualsiasi altra cosa, dovresti specificare la distribuzione che volevi.

+0

Grazie per le linee guida! +1 –

+0

È davvero così difficile? Mi manca qualcosa, perché il mio primo pensiero era usare 'Random.NextBytes' e utilizzare le operazioni bit a bit per applicarli ai bit decimali appropriati per ottenere un valore compreso tra 0 e 1. –

9

Questo dovrebbe farlo. Per i decimali ho utilizzato l'approccio iniziale di Jon Skeet alla generazione casuale di decimal s (nessun vincolo). Per long ho fornito un metodo per la produzione di numeri casuali non negativi long s che viene quindi utilizzato per creare un valore nell'intervallo casuale.

Si noti che per decimal la distribuzione risultante non è una distribuzione uniforme su [minValue, maxValue]. È semplicemente uniforme su tutte le rappresentazioni di bit dei decimali che rientrano nell'intervallo [minValue, maxValue]. Non vedo un modo semplice per aggirare questo problema senza utilizzare il campionamento del rifiuto.

Per long la distribuzione risultante è uniforme su [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

Grazie, questo è un grande frammento (+1) - un paio di domande però: 1. Non perdere informazioni in rg.NextNonNegativeLong()/(double) Int64.MaxValue? Il cast perde precisione e la divisione potrebbe non essere completamente rappresentabile - questi fattori non influenzerebbero l'uniformità e le proprietà? Lo stesso per quanto riguarda NextNonNegativeDecimal()/Decimal.MaxValue 2. In NextInt32, c'è qualche motivo 4 e 28 sono stati selezionati? qualsiasi numero positivo che aggiunge 32 (ad esempio 16 + 16) dovrebbe funzionare, giusto? Grazie! –

+1

Questo codice non può fornire una firma lunga. Chiamando '.NextLong (long.MinValue, long.MaxValue)' restituisce sempre '-9223372036854775808'. –

+0

NextDecimal (decimal.MinValue, 5) genererà OverflowException – Danil

0

Sulla base del metodo di Jon Skeet, ecco il mio pugnalata a lui:

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

fatemi sapere se vedete eventuali errori.

+0

La riduzione di un valore molto grande dividendolo per una potenza di 2 riduce il numero in modo prevedibile. Sarebbe meglio invece rimuovere i bit più significativi, non meno. Prova a spostare in maiuscolo una maschera 1 verso destra finché l'AND bit a bit del valore e la maschera rientrano nell'intervallo. Ottieni una distribuzione molto migliore dei valori casuali. – KeithS

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


use this instead of NextBytes 
+0

-1. I GUID non sono del tutto casuali. Non dovrebbero essere usati al posto di un generatore di numeri pseudocasuali. –

0

Sono venuto qui cercando un modo per generare valori a 64 bit all'interno di un intervallo arbitrario. Le altre risposte non sono riuscite a produrre un numero casuale quando sono stati assegnati determinati intervalli (ad esempio long.MinValue a long.MaxValue). Ecco la mia versione che sembra risolvere il 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))); 
} 

Esso utilizza i seguenti metodi di estensione:

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 distribuzione non è perfettamente anche quando la dimensione del campo richiesto non è un divisore pulita di 2^64, ma fornisce almeno un numero casuale all'interno dell'intervallo di richiesta per qualsiasi intervallo specificato.