IT-Swarm.Net

So generieren Sie eine zufällige Ganzzahl aus einem Bereich

Dies ist eine Fortsetzung einer zuvor veröffentlichten Frage:

Wie generiert man eine Zufallszahl in C?

Ich möchte in der Lage sein, eine Zufallszahl aus einem bestimmten Bereich zu erzeugen, z. B. 1 bis 6, um die Seiten eines Würfels nachzuahmen.

Wie würde ich das machen?

98
Jamie Keeling

Alle bisherigen Antworten sind mathematisch falsch. Das Zurückgeben von Rand() % N ergibt keine einheitliche Zahl im Bereich [0, N), es sei denn N teilt die Länge des Intervalls, in das Rand() zurückgibt (d. H. Eine Potenz von 2). Darüber hinaus hat man keine Ahnung, ob die Module von Rand() unabhängig sind: Es ist möglich, dass sie 0, 1, 2, ... gehen, was einheitlich, aber nicht sehr zufällig ist. Die einzige mögliche Annahme ist, dass Rand() eine Poisson-Verteilung ausgibt: Zwei beliebige nicht überlappende Teilintervalle derselben Größe sind gleich wahrscheinlich und unabhängig. Für eine endliche Menge von Werten impliziert dies eine gleichmäßige Verteilung und stellt auch sicher, dass die Werte von Rand() gut verteilt sind.

Dies bedeutet, dass der Bereich von Rand() nur dann richtig geändert werden kann, wenn er in Kästchen unterteilt wird. Wenn beispielsweise Rand_MAX == 11 und Sie einen Bereich von 1..6 möchten, sollten Sie {0,1} 1, {2,3} 2 usw. zuweisen. Dies sind disjunkte, gleich große Intervalle, die gleichmäßig und unabhängig voneinander verteilt sind.

Der Vorschlag, eine Gleitkommadivision zu verwenden, ist mathematisch plausibel, weist jedoch im Prinzip Rundungsprobleme auf. Vielleicht ist double hoch genug, um es funktionieren zu lassen; vielleicht nicht. Ich weiß es nicht und ich möchte es nicht herausfinden müssen; In jedem Fall ist die Antwort systemabhängig.

Die richtige Methode ist die Verwendung von Ganzzahlarithmetik. Das heißt, Sie möchten Folgendes:

#include <stdlib.h> // For random(), Rand_MAX

// Assumes 0 <= max <= Rand_MAX
// Returns in the closed interval [0, max]
long random_at_most(long max) {
  unsigned long
    // max <= Rand_MAX < ULONG_MAX, so this is okay.
    num_bins = (unsigned long) max + 1,
    num_Rand = (unsigned long) Rand_MAX + 1,
    bin_size = num_Rand / num_bins,
    defect   = num_Rand % num_bins;

  long x;
  do {
   x = random();
  }
  // This is carefully written not to overflow
  while (num_Rand - defect <= (unsigned long)x);

  // Truncated division is intentional
  return x/bin_size;
}

Die Schleife ist notwendig, um eine vollkommen gleichmäßige Verteilung zu erhalten. Wenn Sie beispielsweise Zufallszahlen von 0 bis 2 erhalten und nur solche von 0 bis 1 möchten, ziehen Sie einfach so lange, bis Sie keine 2 mehr erhalten. Es ist nicht schwer zu überprüfen, ob dies mit gleicher Wahrscheinlichkeit 0 oder 1 ergibt. Diese Methode wird auch in dem Link beschrieben, den nos in ihrer Antwort angegeben haben, obwohl sie anders codiert sind. Ich verwende random() anstelle von Rand(), da es eine bessere Verteilung aufweist (wie in der Manpage für Rand() angegeben).

Wenn Sie zufällige Werte außerhalb des Standardbereichs [0, Rand_MAX] erhalten möchten, müssen Sie etwas schwieriges tun. Am zweckmäßigsten ist es vielleicht, eine Funktion random_extended() zu definieren, die n Bits (unter Verwendung von random_at_most()) abzieht und in [0, 2**n) zurückgibt, und dann random_at_most() mit random_extended() anstelle von random() (und 2**n - 1 in von Rand_MAX), um einen Zufallswert kleiner als 2**n abzurufen, vorausgesetzt, Sie haben einen numerischen Typ, der einen solchen Wert enthalten kann. Schließlich können Sie natürlich Werte in [min, max] mit min + random_at_most(max - min) abrufen, einschließlich negativer Werte.

163
Ryan Reich

Schnelles, zuverlässiges und kostengünstiges Cloud-Hosting

Registrieren Sie sich und erhalten Sie innerhalb von 30 Tagen einen Bonus von $50!

Nach der Antwort von @Ryan Reich dachte ich, ich würde meine aufgeräumte Version anbieten. Die Prüfung der ersten Begrenzungen ist angesichts der Prüfung der zweiten Begrenzungen nicht erforderlich, und ich habe sie eher iterativ als rekursiv gemacht. Es gibt Werte im Bereich [min, max] zurück, wobei max >= min und 1+max-min < Rand_MAX.

unsigned int Rand_interval(unsigned int min, unsigned int max)
{
    int r;
    const unsigned int range = 1 + max - min;
    const unsigned int buckets = Rand_MAX / range;
    const unsigned int limit = buckets * range;

    /* Create equal size buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    do
    {
        r = Rand();
    } while (r >= limit);

    return min + (r / buckets);
}
32
theJPster

Hier ist eine Formel, wenn Sie die maximalen und minimalen Werte eines Bereichs kennen und Zahlen innerhalb des Bereichs generieren möchten:

r = (Rand() % (max + 1 - min)) + min
18
Sattar
unsigned int
randr(unsigned int min, unsigned int max)
{
       double scaled = (double)Rand()/Rand_MAX;

       return (max - min +1)*scaled + min;
}

Siehe hier für weitere Optionen.

17
nos

Würdest du nicht einfach tun:

srand(time(NULL));
int r = ( Rand() % 6 ) + 1;

% ist der Modulusoperator. Im Wesentlichen wird es einfach durch 6 geteilt und der Rest wird zurückgegeben ... von 0 - 5

11
Armstrongest

Für diejenigen, die das Problem der Verzerrung verstehen, aber die unvorhersehbare Laufzeit abweisungsbasierter Methoden nicht ertragen können, erzeugt diese Serie im [0, n-1]-Intervall eine progressiv weniger verzerrte Zufallszahl:

r = n / 2;
r = (Rand() * n + r) / (Rand_MAX + 1);
r = (Rand() * n + r) / (Rand_MAX + 1);
r = (Rand() * n + r) / (Rand_MAX + 1);
...

Dazu synthetisiert man eine hochpräzise Festkomma-Zufallszahl von i * log_2(Rand_MAX + 1)-Bits (wobei i die Anzahl der Iterationen ist) und führt eine lange Multiplikation mit n durch.

Wenn die Anzahl der Bits im Vergleich zu n ausreichend groß ist, wird die Vorspannung unermesslich klein.

Es spielt keine Rolle, ob Rand_MAX + 1 kleiner ist als n (wie in diese Frage ), oder wenn es sich nicht um eine Zweierpotenz handelt. Es muss jedoch darauf geachtet werden, dass Integer-Überlauf vermieden wird, wenn Rand_MAX * n groß ist.

8
sh1

Um die Modulo-Neigung (in anderen Antworten vorgeschlagen) zu vermeiden, können Sie immer Folgendes verwenden:

arc4random_uniform(MAX-MIN)+MIN

Dabei ist "MAX" die obere Grenze und "MIN" die untere Grenze. Zum Beispiel für Zahlen zwischen 10 und 20:

arc4random_uniform(20-10)+10

arc4random_uniform(10)+10

Einfache Lösung und besser als "Rand ()% N".

4
magamig

Hier ist ein etwas einfacherer Algorithmus als die Lösung von Ryan Reich:

/// Begin and end are *inclusive*; => [begin, end]
uint32_t getRandInterval(uint32_t begin, uint32_t end) {
    uint32_t range = (end - begin) + 1;
    uint32_t limit = ((uint64_t)Rand_MAX + 1) - (((uint64_t)Rand_MAX + 1) % range);

    /* Imagine range-sized buckets all in a row, then fire randomly towards
     * the buckets until you land in one of them. All buckets are equally
     * likely. If you land off the end of the line of buckets, try again. */
    uint32_t randVal = Rand();
    while (randVal >= limit) randVal = Rand();

    /// Return the position you hit in the bucket + begin as random number
    return (randVal % range) + begin;
}

Example (Rand_MAX := 16, begin := 2, end := 7)
    => range := 6  (1 + end - begin)
    => limit := 12 (Rand_MAX + 1) - ((Rand_MAX + 1) % range)

The limit is always a multiple of the range,
so we can split it into range-sized buckets:
    Possible-Rand-output: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
    Buckets:             [0, 1, 2, 3, 4, 5][0, 1, 2, 3, 4, 5][X, X, X, X, X]
    Buckets + begin:     [2, 3, 4, 5, 6, 7][2, 3, 4, 5, 6, 7][X, X, X, X, X]

1st call to Rand() => 13
    → 13 is not in the bucket-range anymore (>= limit), while-condition is true
        → retry...
2nd call to Rand() => 7
    → 7 is in the bucket-range (< limit), while-condition is false
        → Get the corresponding bucket-value 1 (randVal % range) and add begin
    => 3
4
K. Biermann

Während Ryan richtig ist, kann die Lösung viel einfacher sein, basierend auf dem, was über die Quelle der Zufälligkeit bekannt ist. Um das Problem erneut zu beschreiben: 

  • Es gibt eine Zufallsquelle, bei der ganzzahlige Zahlen im Bereich [0, MAX) mit gleichmäßiger Verteilung ausgegeben werden. 
  • Ziel ist es, gleichmäßig verteilte Zufallszahlen im Bereich [rmin, rmax] zu erzeugen, wobei 0 <= rmin < rmax < MAX.

Meiner Erfahrung nach ist und die ursprüngliche Quelle kryptographisch stark, wenn die Anzahl der Fächer (oder "Boxen") deutlich kleiner als der Bereich der ursprünglichen Zahlen ist. Es ist nicht notwendig, all diese Rigamarole zu durchlaufen Einfache Modulo-Division würde genügen (wie output = rnd.next() % (rmax+1), wenn rmin == 0) und Zufallszahlen erzeugen, die gleichmäßig "ausreichend" und ohne Geschwindigkeitsverlust verteilt werden. Der Schlüsselfaktor ist die Zufallsquelle (d. H. Kinder, versuchen Sie es nicht zu Hause mit Rand()).

Hier ist ein Beispiel/Beweis dafür, wie es in der Praxis funktioniert. Ich wollte Zufallszahlen von 1 bis 22 generieren, mit einer kryptografisch starken Quelle, die Zufallsbytes (basierend auf Intel RDRAND) erzeugt hat. Die Ergebnisse sind:

Rnd distribution test (22 boxes, numbers of entries in each box):     
 1: 409443    4.55%
 2: 408736    4.54%
 3: 408557    4.54%
 4: 409125    4.55%
 5: 408812    4.54%
 6: 409418    4.55%
 7: 408365    4.54%
 8: 407992    4.53%
 9: 409262    4.55%
10: 408112    4.53%
11: 409995    4.56%
12: 409810    4.55%
13: 409638    4.55%
14: 408905    4.54%
15: 408484    4.54%
16: 408211    4.54%
17: 409773    4.55%
18: 409597    4.55%
19: 409727    4.55%
20: 409062    4.55%
21: 409634    4.55%
22: 409342    4.55%   
total: 100.00%

Dies ist so nahe an der Uniform, wie ich es für meine Zwecke brauche (fairer Würfelwurf, kryptografisch starke Codebücher für Verschlüsselungsmaschinen aus dem Zweiten Weltkrieg, wie http://users.telenet.be/d.rijmenants/de/kl-7sim). htm , etc). Die Ausgabe zeigt keine nennenswerte Verzerrung.

Hier ist die Quelle für den kryptographisch starken (echten) Zufallszahlengenerator: Intel Digital Random Number Generator Und ein Beispielcode, der 64-Bit-Zufallszahlen (unsigned) erzeugt.

int rdrand64_step(unsigned long long int *therand)
{
  unsigned long long int foo;
  int cf_error_status;

  asm("rdrand %%rax; \
        mov $1,%%edx; \
        cmovae %%rax,%%rdx; \
        mov %%edx,%1; \
        mov %%rax, %0;":"=r"(foo),"=r"(cf_error_status)::"%rax","%rdx");
        *therand = foo;
  return cf_error_status;
}

Ich habe es auf Mac OS X mit clang-6.0.1 (gerade) und mit gcc-4.8.3 unter Verwendung des Flag "-Wa, q" kompiliert (da GAS diese neuen Anweisungen nicht unterstützt).

2
Mouse

Wie gesagt, ist Modulo nicht ausreichend, da es die Verteilung verzerrt. Hier ist mein Code, der Bits abdeckt und sie verwendet, um sicherzustellen, dass die Verteilung nicht verdreht wird.

static uint32_t randomInRange(uint32_t a,uint32_t b) {
    uint32_t v;
    uint32_t range;
    uint32_t upper;
    uint32_t lower;
    uint32_t mask;

    if(a == b) {
        return a;
    }

    if(a > b) {
        upper = a;
        lower = b;
    } else {
        upper = b;
        lower = a; 
    }

    range = upper - lower;

    mask = 0;
    //XXX calculate range with log and mask? nah, too lazy :).
    while(1) {
        if(mask >= range) {
            break;
        }
        mask = (mask << 1) | 1;
    }


    while(1) {
        v = Rand() & mask;
        if(v <= range) {
            return lower + v;
        }
    }

}

Mit dem folgenden einfachen Code können Sie die Verteilung betrachten:

int main() {

    unsigned long long int i;


    unsigned int n = 10;
    unsigned int numbers[n];


    for (i = 0; i < n; i++) {
        numbers[i] = 0;
    }

    for (i = 0 ; i < 10000000 ; i++){
        uint32_t Rand = random_in_range(0,n - 1);
        if(Rand >= n){
            printf("bug: Rand out of range %u\n",(unsigned int)Rand);
            return 1;
        }
        numbers[Rand] += 1;
    }

    for(i = 0; i < n; i++) {
        printf("%u: %u\n",i,numbers[i]);
    }

}
1
Andrew Chambers

Gibt eine Fließkommazahl im Bereich [0,1] zurück:

#define Rand01() (((double)random())/((double)(Rand_MAX)))
0
Geremia