Waarom produceren rand() + rand() negatieve getallen?

Ik merkte op dat de bibliotheekfunctie rand()wanneer deze slechts één keer binnen een lus wordt aangeroepen, deze bijna altijd positieve getallen produceert.

for (i = 0; i < 100; i++) {
    printf("%d\n", rand());
}

Maar als ik twee rand()-aanroepen toevoeg, hebben de gegenereerde getallen nu meer negatieve getallen.

for (i = 0; i < 100; i++) {
    printf("%d = %d\n", rand(), (rand() + rand()));
}

Kan iemand mij uitleggen waarom ik in het tweede geval negatieve getallen zie?

PS: ik initialiseer de seed voor de lus als srand(time(NULL)).


Antwoord 1, autoriteit 100%

rand()wordt gedefinieerd om een ​​geheel getal tussen 0en RAND_MAXte retourneren.

rand() + rand()

kan overlopen. Wat u waarneemt, is waarschijnlijk het resultaat van ongedefinieerd gedragveroorzaakt door integer overflow.


Antwoord 2, autoriteit 17%

Het probleem is de toevoeging. rand()geeft een intwaarde van 0...RAND_MAX. Dus als je er twee toevoegt, krijg je tot RAND_MAX * 2. Als dat INT_MAXoverschrijdt, overschrijdt het resultaat van de toevoeging het geldige bereik dat een intkan bevatten. Overloop van ondertekende waarden is ongedefinieerd gedrag en kan ertoe leiden dat uw toetsenbord in vreemde talen tegen u praat.

Omdat het hier geen winst oplevert om twee willekeurige resultaten toe te voegen, is het simpele idee om het gewoon niet te doen. Als alternatief kunt u elk resultaat casten naar unsigned intvóór de toevoeging als dat de som kan bevatten. Of gebruik een groter type. Merk op dat longniet noodzakelijk breder is dan int, hetzelfde geldt voor long longals intminimaal 64 is stukjes!

Conclusie: vermijd gewoon de toevoeging. Het geeft niet meer “willekeur”. Als je meer bits nodig hebt, kun je de waarden sum = a + b * (RAND_MAX + 1)samenvoegen, maar daarvoor is waarschijnlijk ook een groter gegevenstype nodig dan int.

Omdat je aangegeven reden is om een ​​nulresultaat te vermijden: dat kan niet worden vermeden door de resultaten van twee rand()-aanroepen toe te voegen, aangezien beide nul kunnen zijn. In plaats daarvan kunt u gewoon verhogen. Als RAND_MAX == INT_MAX, kan dit niet in int. Echter, (unsigned int)rand() + 1zal het zeer, zeer waarschijnlijk doen. Waarschijnlijk (niet definitief), omdat het UINT_MAX > INT_MAX, wat geldt voor alle implementaties die ik ken (die heel wat embedded architecturen, DSP’s en alle desktop-, mobiele en serverplatforms van de afgelopen 30 jaar omvat).

Waarschuwing:

Hoewel het hier al in opmerkingen is vermeld, houd er rekening mee dat het toevoegen van twee willekeurige waarden geeneen uniforme verdeling oplevert, maar een driehoekige verdeling zoals het gooien van twee dobbelstenen: om 12te krijgen (twee dobbelstenen) beide dobbelstenen moeten 6tonen. voor 11zijn er al twee mogelijke varianten: 6 + 5of 5 + 6, enz.

Dus de toevoeging is vanuit dit oogpunt ook slecht.

Houd er rekening mee dat de resultaten die rand()genereert niet onafhankelijk van elkaar zijn, omdat ze worden gegenereerd door een pseudo-willekeurige nummergenerator. Merk ook op dat de norm niet de kwaliteit of uniforme verdeling van de berekende waarden specificeert.


Antwoord 3, autoriteit 7%

Dit is een antwoord op een verduidelijking van de vraag in een reactie op dit antwoord,

de reden dat ik toevoegde was om ‘0’ te vermijden als het willekeurige getal in mijn code. rand()+rand() was de snelle vuile oplossing die snel in me opkwam.

Het probleem was om 0 te vermijden. Er zijn (minstens) twee problemen met de voorgestelde oplossing. Een daarvan is, zoals de andere antwoorden aangeven, dat rand()+rand()ongedefinieerd gedrag kan oproepen. Het beste advies is om nooit ongedefinieerd gedrag op te roepen. Een ander probleem is dat er geen garantie is dat rand()niet twee keer achter elkaar 0 zal produceren.

Het volgende verwerpt nul, vermijdt ongedefinieerd gedrag en zal in de overgrote meerderheid van de gevallen sneller zijn dan twee aanroepen naar rand():

int rnum;
for (rnum = rand(); rnum == 0; rnum = rand()) {}
// or do rnum = rand(); while (rnum == 0);

Antwoord 4

In principe produceren rand()getallen tussen 0en RAND_MAX, en 2 RAND_MAX > INT_MAXin jouw geval.

U kunt modulus maken met de maximale waarde van uw gegevenstype om overflow te voorkomen. Dit verstoort natuurlijk de verdeling van de willekeurige getallen, maar randis slechts een manier om snel willekeurige getallen te krijgen.

#include <stdio.h>
#include <limits.h>
int main(void)
{
    int i=0;
    for (i=0; i<100; i++)
        printf(" %d : %d \n", rand(), ((rand() % (INT_MAX/2))+(rand() % (INT_MAX/2))));
    for (i=0; i<100; i++)
        printf(" %d : %ld \n", rand(), ((rand() % (LONG_MAX/2))+(rand() % (LONG_MAX/2))));
    return 0;
}

Antwoord 5

Misschien zou je een nogal lastige benadering kunnen proberen door ervoor te zorgen dat de waarde die wordt geretourneerd door de som van 2 rand() nooit de waarde van RAND_MAX overschrijdt. Een mogelijke benadering zou kunnen zijn: som = rand()/2 + rand()/2; Dit zou ervoor zorgen dat voor een 16-bits compiler met een RAND_MAX-waarde van 32767, zelfs als beide rand toevallig 32767 retourneert, zelfs dan (32767/2 = 16383) 16383+16383 = 32766, dus niet zou resulteren in een negatieve som.


Antwoord 6

de reden dat ik toevoegde was om ‘0’ te vermijden als het willekeurige getal in mijn code. rand()+rand() was de snelle vuile oplossing die snel in me opkwam.

Een eenvoudige oplossing (oké, noem het een “Hack”) die nooit een nulresultaat oplevert en nooit zal overlopen is:

x=(rand()/2)+1    // using divide  -or-
x=(rand()>>1)+1   // using shift which may be faster
                  // compiler optimization may use shift in both cases

Dit zal je maximale waarde beperken, maar als je daar niet om geeft, dan zou dit prima moeten werken voor jou.


Antwoord 7

Probeer het volgende om 0 te vermijden:

int rnumb = rand()%(INT_MAX-1)+1;

U moet limits.hopnemen.


Antwoord 8

thx. de reden dat ik toevoegde was om ‘0’ te vermijden als het willekeurige getal in mijn code. rand()+rand() was de snelle vuile oplossing die snel in me opkwam

Het klinkt als een XY-probleem, waarbij je, om geen 0 te krijgen van rand(), rand()twee keer aanroept, waarbij je de programma langzamer, met een nieuwe tegenslag en de mogelijkheid om een ​​0 te krijgen is er nog steeds.

Een andere oplossing is het gebruik van uniform_int_distribution, die een willekeurig en uniform verdeeld getal creëert in het gedefinieerde interval:

https://wandbox.org/permlink/QKIHG4ghwJf1b7ZN

#include <random>
#include <array>
#include <iostream>
int main()
{
    const int MAX_VALUE=50;
    const int MIN_VALUE=1;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distrib(MIN_VALUE, MAX_VALUE);
    std::array<int,MAX_VALUE-MIN_VALUE> weight={0};
    for(int i=0; i<50000; i++) {
        weight[distrib(gen)-MIN_VALUE]++;
    }
    for(int i=0;i<(int)weight.size();i++) {
        std::cout << "value: " << MIN_VALUE+i << " times: " << weight[i] << std::endl; 
    }
}

Antwoord 9

Terwijl wat iedereen heeft gezegd over de waarschijnlijke overloop, zou dat heel goed de oorzaak kunnen zijn van het negatieve, zelfs als je niet-ondertekende gehele getallen gebruikt. Het echte probleem is eigenlijk het gebruik van tijd/datum-functionaliteit als het zaad. Als je echt vertrouwd bent geraakt met deze functionaliteit, weet je precies waarom ik dit zeg. Wat het echt doet, is een afstand (verstreken tijd) geven sinds een bepaalde datum/tijd. Hoewel het gebruik van de datum/tijd-functionaliteit als het begin van een rand(), een veel voorkomende praktijk is, is het echt niet de beste optie. Je zou betere alternatieven moeten zoeken, want er zijn veel theorieën over het onderwerp en ik zou onmogelijk op ze allemaal kunnen ingaan. Je voegt aan deze vergelijking de mogelijkheid van overloop toe en deze aanpak was vanaf het begin gedoemd te mislukken.

Degenen die de rand()+1 hebben gepost, gebruiken de oplossing die het meest wordt gebruikt om te garanderen dat ze geen negatief getal krijgen. Maar die aanpak is ook echt niet de beste manier.

Het beste wat je kunt doen is de extra tijd nemen om te schrijven en de juiste exception handling te gebruiken, en alleen toevoegen aan het rand() nummer als en/of wanneer je een nul resultaat krijgt. En om goed om te gaan met negatieve getallen. De rand()-functionaliteit is niet perfect en moet daarom worden gebruikt in combinatie met het afhandelen van uitzonderingen om ervoor te zorgen dat u het gewenste resultaat krijgt.

De extra tijd en moeite nemen om de rand()-functionaliteit te onderzoeken, bestuderen en correct te implementeren, is de tijd en moeite zeker waard. Gewoon mijn twee cent. Veel succes met je inspanningen…

Other episodes