Neden rand () + rand () negatif sayılar üretir?


304

rand()Kütüphane fonksiyonunun bir döngü içinde sadece bir kez çağrıldığında neredeyse her zaman pozitif sayılar ürettiğini gözlemledim .

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

Ancak iki rand()arama eklediğimde , üretilen numaraların artık daha fazla negatif numarası var.

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

Birisi ikinci durumda neden negatif sayılar gördüğümü açıklayabilir mi?

PS: Ben döngü önce tohum olarak başlatmak srand(time(NULL)).


11
rand()negatif olamaz ...
yirmi mayıs

293
rand () + rand ()
owerflow

13
Nedir RAND_MAXsizin derleyici için? Genellikle içinde bulabilirsiniz stdlib.h. (Komik: kontrol man 3 rand, tek satırlık açıklama "kötü rasgele sayı üreteci"
taşır

6
her aklı başında programcının yapacağı şeyi yapın abs(rand()+rand()). Negatif olandan daha olumlu bir UB'ye sahip olmayı tercih ederim! ;)
Vinicius Kamakura

11
@hexa: bu, ekleme için halihazırda olduğu gibi, UB için bir sıkıntı değildir. UB'nin tanımlanmış davranış haline gelmesini sağlayamazsınız . Bir aklı başında progrtammer cehennem gibi UB önleyeceğini.
Bu site için çok dürüst

Yanıtlar:


542

rand(), 0ve arasında bir tamsayı döndürmek için tanımlanır RAND_MAX.

rand() + rand()

taşabilir. Gözlemlediğiniz şey , tamsayı taşması nedeniyle tanımlanmamış davranışların bir sonucudur .


4
@JakubArnold: Taşma davranışı her dil tarafından farklı şekilde nasıl belirlenir? Örneğin int sadece büyüdükçe Python'da hiç yok (kullanılabilir belleğe kadar).
Bu site için çok dürüst

2
@Olaf Bir dilin işaretli tam sayıları temsil etmeye nasıl karar verdiğine bağlıdır. Java'nın tamsayı taşmasını algılayacak bir mekanizması yoktu (java 8'e kadar) ve etrafı sarmak için tanımladı ve Go sadece 2'nin tamamlayıcı gösterimini kullanıyor ve imzalı tamsayı taşmaları için yasal olarak tanımlıyor. C, 2'den fazla tamamlayıcıyı destekliyor.
PP

2
@EvanCarslake Hayır, bu evrensel bir davranış değil. Söylediğin şey 2'nin tamamlayıcı temsilidir. Ancak C dili de diğer temsillere izin verir. C dili belirtimi, işaretli tamsayı taşmasının tanımsız olduğunu söylüyor . Bu nedenle, genel olarak, hiçbir program bu tür davranışlara güvenmemeli ve imzalı tamsayı taşmasına neden olmamak için dikkatlice kodlamaya ihtiyaç duymamalıdır. Ancak bu, işaretsiz tamsayılar için, iyi tanımlanmış (indirgeme modulo 2) bir şekilde "sarıp sarmalayabilecekleri" için geçerli değildir. [devamı] ...
PP

12
İşaretli tamsayı taşmasıyla ilgili C standardından alıntı: Bir ifadenin değerlendirilmesi sırasında (yani, sonuç matematiksel olarak tanımlanmamışsa veya türü için temsil edilebilir değerler aralığında değilse) istisnai bir durum ortaya çıkarsa, davranış tanımsız.
PP

3
@EvanCarslake, C derleyicilerinin standardı kullandıkları sorudan biraz uzaklaşıyor ve işaretli tamsayılar için bunu a + b > abiliyorlarsa varsayabilirler b > 0. Ayrıca, daha sonra yürütülen bir ifade a + 5varsa, geçerli değerin o zamandan daha düşük olduğunu varsayabilirler INT_MAX - 5. Bu nedenle, 2'nin tamamlayıcı işlemcisi / tuzaksız yorumlayıcı programı bile, ints'nin tuzaksız tamamlayıcısı gibi davranmayabilir .
Maciej Piechotka

90

Sorun ekleme. değerini rand()döndürür . Yani, ikisini eklerseniz, yukarı çıkacaksınız . Eğer bu aşılırsa , ekleme sonucu a'nın alabileceği geçerli aralığın üzerine taşar . İmzalanan değerlerin taşması tanımlanmamış bir davranıştır ve klavyenizin sizinle yabancı dillerde konuşmasına neden olabilir.int0...RAND_MAXRAND_MAX * 2INT_MAXint

Burada iki rastgele sonuç eklemede kazanç olmadığı için, basit fikir bunu yapmamaktır. Alternatif olarak unsigned int, toplamı tutabilirse, her bir sonucu ekleme işleminden önce yayınlayabilirsiniz. Veya daha büyük bir tip kullanın. Her longzamankinden daha geniş olmadığına intdikkat long longedin int, en az 64 bit ise aynı durum geçerlidir !

Sonuç: Sadece eklemekten kaçının. Daha fazla "rastgelelik" sağlamaz. Daha fazla bite ihtiyacınız varsa, değerleri birleştirebilirsiniz sum = a + b * (RAND_MAX + 1), ancak bu muhtemelen daha büyük bir veri türü gerektirir int.

Belirttiğiniz neden sıfır sonuçtan kaçınmak olduğu için: İki rand()aramanın sonuçlarını ekleyerek her ikisi de sıfır olabileceğinden bu önlenemez . Bunun yerine, arttırabilirsiniz. Eğer RAND_MAX == INT_MAX, bu yapılamaz int. Ancak, (unsigned int)rand() + 1çok, çok muhtemel yapacak. Muhtemelen (kesin olarak değil), çünkü gerektirdiği UINT_MAX > INT_MAX, farkında olduğum tüm uygulamalar için geçerlidir (oldukça gömülü mimarileri, DSP'leri ve son 30 yılın tüm masaüstü, mobil ve sunucu platformlarını kapsar).

Uyarı:

Zaten burada yorumlarda serpiştirilmiş rağmen, iki rasgele değerler ekleyerek yok lütfen not o değil almak için: tek tip bir dağılım elde, ancak iki zar haddeleme gibi üçgen dağılım 12hem zar göstermek zorunda (iki zar) 6. çünkü 11zaten iki olası varyant vardır: 6 + 5veya 5 + 6vb.

Yani, ekleme de bu açıdan kötüdür.

Ayrıca rand(), bir psödondom sayı üreteci tarafından üretildiklerinden , sonuçların birbirinden bağımsız olmadığını da unutmayın . Standardın hesaplanan değerlerin kalitesini veya tekdüze dağılımını belirtmediğini de unutmayın.


14
@badmad: Her iki çağrı da 0 döndürürse ne olur?
Bu site için çok dürüst

3
@badmad: UINT_MAX > INT_MAX != falseStandart tarafından temin edilip edilmediğini merak ediyorum . (Büyük olasılıkla geliyor, ancak gerekirse emin değilim). Eğer öyleyse, sadece tek bir sonuç ve artış (bu sırayla!)
Bu site için çok dürüst

3
Tekdüze olmayan bir dağıtım istediğinizde birden fazla rasgele sayı eklemenin kazancı vardır: stackoverflow.com/questions/30492259/…
Cœur

6
0 önlemek için, basit bir "sonuç 0 iken, yeniden rulo"?
Olivier Dulac

2
Sadece 0'dan kaçınmak için kötü bir yol eklemekle kalmaz, aynı zamanda düzgün olmayan bir dağılımla sonuçlanır. Yuvarlanan zarların sonuçları gibi bir dağılım elde edersiniz: 7, 2 veya 12'den 6 kat daha olasıdır
Barmar

36

Bu, bu cevaba yapılan yorumda yapılan sorunun açıklığa kavuşturulması için bir cevaptır ,

i eklemek nedeni benim kod rastgele sayı olarak '0' önlemek oldu. rand () + rand () kolayca aklıma gelen hızlı kirli çözüm oldu.

Sorun 0'dan kaçınmaktı. Önerilen çözümde (en azından) iki problem var. Birincisi, diğer cevapların da belirttiği gibi, rand()+rand()tanımsız davranışları çağırabilir. En iyi tavsiye, asla tanımsız davranışları harekete geçirmektir. Başka bir sorun, rand()arka arkaya iki kez 0 üretmeyecek garantisi yoktur .

Aşağıdaki sıfırı reddeder, tanımlanmamış davranışı önler ve vakaların büyük çoğunluğunda iki çağrıdan daha hızlı olacaktır rand():

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

9
Ne olmuş rand() + 1?
askvictor

3
@askvictor Bu taşabilir (olası olmasa da).
gerrit

3
@gerrit - MAX_INT ve RAND_MAX'a bağlı
askvictor

3
@gerrit, onlar ise sürpriz olacağını değil aynı, ama bu :) bilgiçler için bir yer olduğunu varsayalım
askvictor

10
RAND_MAX == MAX_INT ise, rand () + 1, rand () değeri 0 ile tam olarak aynı olasılıkla taşacak ve bu da bu çözümü tamamen anlamsız hale getirecektir. Risk almak ve taşma olasılığını göz ardı etmek istiyorsanız, rand () 'yi olduğu gibi kullanabilir ve 0 döndürme olasılığını yoksayabilirsiniz.
Emil Jeřábek

3

Temelde rand()arasındaki sayılar üretmek 0ve RAND_MAXve 2 RAND_MAX > INT_MAXsenin durumunda.

Taşmayı önlemek için veri türünüzün maksimum değeriyle modül oluşturabilirsiniz. Bu tabii ki rasgele sayıların dağılımını bozar, ancak randhızlı rasgele sayılar elde etmenin bir yoludur.

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

2

2 rand () toplamının döndürdüğü değerin hiçbir zaman RAND_MAX değerini aşmamasını sağlayarak oldukça zor bir yaklaşım deneyebilirsiniz. Olası bir yaklaşım toplam = rand () / 2 + rand () / 2 olabilir; Bu, RAND_MAX değeri 32767 olan 16 bitlik bir derleyici için, her iki rand da 32767'ye geri dönse bile (32767/2 = 16383) 16383 + 16383 = 32766, bu nedenle negatif toplamla sonuçlanmaz.


1
OP, 0'ı sonuçlardan hariç tutmak istedi. Ekleme ayrıca rastgele değerlerin düzgün bir dağılımını sağlamaz.
Bu site için çok dürüst

@Olaf: İki ardışık çağrının rand()ikisinin de sıfır vermeyeceğine dair bir garanti yoktur , bu nedenle sıfırdan kaçınma arzusu iki değer eklemek için iyi bir neden değildir. Öte yandan, homojen olmayan bir dağılıma sahip olma arzusu, taşmanın gerçekleşmemesini sağlarsa, iki rastgele değer eklemek için iyi bir neden olacaktır.
supercat

1

i eklemek nedeni benim kod rastgele sayı olarak '0' önlemek oldu. rand () + rand () kolayca aklıma gelen hızlı kirli çözüm oldu.

Asla sıfır sonuç vermeyen ve asla taşmayacak basit bir çözüm (tamam, "Hack" olarak adlandırın):

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

Bu, maksimum değerinizi sınırlar, ancak bunu umursamıyorsanız, bu sizin için iyi çalışmalıdır.


1
Sidenote: İmzalı değişkenlerin doğru kaydırılmasında dikkatli olun. Yalnızca negatif olmayan değerler için iyi tanımlanmıştır, negatifler için uygulama tanımlanmıştır. (Neyse ki, rand()her zaman negatif olmayan bir değer döndürür). Ancak, optimizasyonu burada derleyiciye bırakacağım.
Bu site için çok dürüst

@Olaf: Genel olarak, imzalı ikiye bölünme, bir vardiyadan daha az verimli olacaktır. Derleyici yazar derleyiciye randnegatif olmayacağını söyleme çabası göstermedikçe , vardiya imzalı bir tamsayı 2'nin bölünmesinden daha etkili olacaktır. Bölme 2uişe yarayabilir, ancak eğer bir işaret xise int, imzasızların örtük dönüşümü ile ilgili uyarılara neden olabilir. imzalı.
supercat

@supercat: Lütfen yorumumu car3efully tekrar okuyun. Herhangi bir makul derleyici / 2zaten bir vardiya kullanacağını çok iyi bilmelisiniz (Bunu -O0açıkça bir şey istendiğinde, örneğin optimizasyonlar olmadan gördüm ). Muhtemelen C kodunun en önemsiz ve en köklü optimizasyonu. Nokta, bölünmenin sadece tamsayı olmayan değerler için değil, tamsayı aralığı için standart tarafından iyi tanımlanmış olmasıdır. Tekrar: derleyiciye optimizasyon bırakın , ilk etapta doğru ve net kod yazın. Bu yeni başlayanlar için daha da önemlidir.
Bu site için çok dürüst

@Olaf: Test ettiğim her derleyici rand()sağa doğru kaydırırken veya bölerken 2u, kullanırken bile 2'ye böldüğünden daha verimli kod üretir -O3. Makul bir şekilde, bu tür bir optimizasyonun önemli olmadığını söyleyebiliriz, ancak "bu tür optimizasyonları derleyiciye bırak" demek, derleyicilerin bunları gerçekleştirebileceğini ima eder. Gerçekten olacak herhangi bir derleyici biliyor musunuz ?
supercat

@supercat: O zaman daha modern derleyiciler kullanmalısınız. gcc sadece oluşturulan Assembler son kontrol kez ince kod üretti. Yine de, bir groopie'ye sahip olduğum kadar, son kez sunacağınız uzunluğa taciz edilmemeyi tercih ederim. Bu yayınlar yaşında, yorumlarım tamamen geçerli. Teşekkür ederim.
Bu site için çok dürüst

1

0'ı önlemek için şunu deneyin:

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

Eklemeniz gerekiyor limits.h.


4
Yani Temelde aynı olan (ancak possiblly yavaş) şartlı 1 ekleyerek 1. almak için olasılık iki katına eğer rand()verimleri 0.
bu site için çok dürüst

Evet, haklısın Olaf. Rand () = 0 veya INT_MAX -1 ise rnumb 1 olacaktır.
Doni

Daha da kötüsü, ben düşünmeye gelirken. Aslında 1ve 2(hepsi varsayılır RAND_MAX == INT_MAX) için iki katına çıkacaktır . Ben unutmuşum - 1.
Bu site için çok dürüst

1
-1Burada hiçbir değeri vermektedir. rand()%INT_MAX+1; yine de yalnızca [1 ... INT_MAX] aralığında değerler oluşturur.
chux - Monica

-2

İmzasız tamsayı kullansanız bile, herkesin olası taşma hakkında söyledikleri negatifin nedeni olabilir. Asıl sorun aslında tohum olarak saat / tarih işlevini kullanmaktır. Bu işlevselliğe gerçekten aşina olduysanız, bunu neden söylediğimi tam olarak bileceksiniz. Gerçekten yaptığı gibi, belirli bir tarih / saatten bu yana bir mesafe (geçen süre) vermektir. Tarih / saat işlevselliğinin bir rand () tohumu olarak kullanımı çok yaygın bir uygulama olsa da, gerçekten en iyi seçenek değildir. Konuyla ilgili birçok teori olduğundan ve muhtemelen hepsine giremediğim için daha iyi alternatifler aramalısınız. Bu denkleme taşma olasılığını eklersiniz ve bu yaklaşım baştan mahkumdur.

Rand () + 1'i yayınlayanlar, negatif bir sayı almamalarını garanti etmek için en çok kullanılan çözümü kullanıyorlar. Ancak, bu yaklaşım da en iyi yol değildir.

Yapabileceğiniz en iyi şey, uygun istisna işlemeyi yazmak ve kullanmak için fazladan zaman ayırmaktır ve sadece sıfır sonuçla sonuçlanırsa ve / veya sonunda rand () numarasına ekleyin. Ve negatif sayılarla düzgün bir şekilde başa çıkmak için. Rand () işlevselliği mükemmel değildir ve bu nedenle istenen sonucu elde etmenizi sağlamak için istisna işleme ile birlikte kullanılması gerekir.

Rand () işlevselliğini araştırmak, incelemek ve uygun şekilde uygulamak için fazladan zaman ve çaba harcamak zaman ve çabaya değer. Sadece iki sentim. Çalışmalarınızda iyi şanslar ...


2
rand()hangi tohumun kullanılacağını belirtmez. Standart , herhangi bir zamanla bir ilişki değil, bir sözde jeneratör üreteceğini belirtir. Ayrıca jeneratörün kalitesi hakkında da bilgi vermez. Asıl sorun açıkça taşmadır. Bunu rand()+1önlemek için kullanılır 0; rand()negatif bir değer döndürmüyor. Üzgünüm, ama buradaki noktayı kaçırdınız. PRNG'nin kalitesi ile ilgili değildir. ...
Bu site için çok dürüst

... GNU / Linux altında iyi uygulama yapmak /dev/randomve daha sonra iyi bir PRNG kullanmak ( rand()glibc kalitesinden emin değilim ) veya cihazı kullanmaya devam etmek - yeterli entropi yoksa, uygulamanızı riske atmak. Entropinizi uygulamaya sokmaya çalışmak, saldırıya uğraması daha kolay olduğu için bir güvenlik açığı olabilir. Ve şimdi sertleşmeye geliyor - burada değil
bu site için çok dürüst
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.