Bu örnek kod std::rand
, her gördüğünüzde kaşlarınızı kaldırması gereken eski bir kargo kültü balderdash vakası olduğunu göstermektedir .
Burada birkaç sorun var:
Sözleşme insanlar genellikle yoksul talihsiz ruhlar daha iyi bilmeyen bile-varsayalım ve hassas bunların içinde düşünmek olmaz terimler-olduğu rand
gelen numuneler homojen dağılımı 0 yılında tamsayılar üzerinde, 1, 2, ..., RAND_MAX
, ve her çağrı bağımsız bir örnek verir .
İlk sorun, varsayılan sözleşmenin, her çağrıdaki bağımsız tek tip rastgele örneklemlerin aslında dokümantasyonun söylediği gibi olmamasıdır - ve pratikte, uygulamalar tarihsel olarak bağımsızlık için en yalın simülasyonu bile sağlamada başarısız olmuştur. Örneğin, C99 §7.20.2.1 ' rand
Fonksiyon' detaylandırmadan şunu söyler:
rand
Fonksiyonu, aralık 0 yalancı rasgele bir tamsayı dizisi hesaplar RAND_MAX
.
Bu anlamsız bir cümle, çünkü sözde raslantısallık bir işlevin (veya işlevler ailesinin ) bir özelliğidir , bir tam sayı değildir, ancak bu, ISO bürokratlarının bile dili kötüye kullanmasını engellemez. Ne de olsa, bundan rahatsız olacak tek okuyucular rand
, beyin hücrelerinin bozulmasından korktukları için belgeleri okumaktan daha iyisini biliyorlar .
C'deki tipik bir tarihsel uygulama şu şekilde çalışır:
static unsigned int seed = 1;
static void
srand(unsigned int s)
{
seed = s;
}
static unsigned int
rand(void)
{
seed = (seed*1103515245 + 12345) % ((unsigned long)RAND_MAX + 1);
return (int)seed;
}
Bu, talihsiz bir özelliğe sahiptir; tek bir örnek, tek tip bir rastgele çekirdek altında tekdüze olarak dağıtılsa bile (özel değerine bağlıdır RAND_MAX
), ardışık çağrılarda çift ve tek tamsayılar arasında değişir - sonra
int a = rand();
int b = rand();
ifade (a & 1) ^ (b & 1)
% 100 olasılıkla 1 verir; bu, çift ve tek tam sayılarda desteklenen herhangi bir dağılımdaki bağımsız rastgele örnekler için geçerli değildir . Böylelikle, 'daha iyi rastgeleliğin' yakalanması zor canavarını kovalamak için düşük dereceli bitleri atılması gereken bir kargo kültü ortaya çıktı. (Spoiler uyarısı: Bu teknik bir terim değildir. Bu, okuduğunuz her yerde nesrin ne hakkında konuştuğunu bilmediğini veya sizin fikriniz olmadığını ve küçümsendiğinizi düşündüğünün bir işaretidir .)
İkinci problem ise, her çağrı 0, 1, 2,…, üzerinde tek tip bir rastgele dağılımdan bağımsız olarak örnekleme yapsa bileRAND_MAX
, sonucunun rand() % 6
bir kalıp gibi 0, 1, 2, 3, 4, 5'e eşit olarak dağıtılmamasıdır. yuvarlama, RAND_MAX
-1 modulo 6 ile uyumlu olmadığı sürece . Basit karşı örnek: Eğer RAND_MAX
= 6 ise rand()
, tüm sonuçların 1/7 olasılığa eşit olması, ancak başlangıçtaki rand() % 6
sonuçların 2/7 olasılığı varken diğer tüm sonuçların 1/7 olasılığı .
Bunu yapmanın doğru yolu ret örnekleme ile geçerli: defalarca bağımsız tekdüze rasgele bir numune alın s
0, 1, 2, ..., RAND_MAX
ve reddetmek sonuçları 0, 1, 2, ..., (örneğin) ((RAND_MAX + 1) % 6) - 1
Eğer birini almak -eğer baştan başlayın; aksi takdirde verim s % 6
.
unsigned int s;
while ((s = rand()) < ((unsigned long)RAND_MAX + 1) % 6)
continue;
return s % 6;
Bu şekilde, rand()
kabul ettiğimiz sonuçlar kümesi 6'ya eşit olarak bölünebilir ve her bir olası sonuç s % 6
, aynı sayıda kabul edilen sonuç tarafından elde edilir rand()
, bu nedenle rand()
tek tip olarak dağıtılırsa öyle olur s
. Deneme sayısında sınır yoktur , ancak beklenen sayı 2'den azdır ve başarı olasılığı deneme sayısıyla katlanarak artar.
Seçimi ait sonuçlarını siz cppreference.com kod bir yapar 6. Aşağıdaki her tam sayıya bunların eşit sayıda harita şartıyla önemsizdir reddetmek farklı bir şey hakkında garanti olduğunu yukarıda çünkü ilk sorunun, seçim çıktıların dağılımı veya bağımsızlığı ve pratikte düşük sıralı bitler 'yeterince rasgele görünmeyen' örüntüler sergiledi (bir sonraki çıktının bir öncekinin deterministik bir işlevi olduğunu unutmayın).rand()
rand()
Okuyucu için alıştırma: cppreference.com'daki kodun, rand()
0, 1, 2,…, üzerinde düzgün bir dağılım sağlaması halinde, kalıp silindirleri üzerinde düzgün bir dağılım sağladığını kanıtlayın RAND_MAX
.
Okuyucu için alıştırma: Neden bir veya diğer alt kümelerin reddetmesini tercih edebilirsiniz? İki durumda her bir deneme için hangi hesaplama gereklidir?
Üçüncü bir problem, tohum boşluğunun o kadar küçük olmasıdır ki, tohum tekdüze olarak dağıtılmış olsa bile, programınız ve bir sonuç hakkında bilgi sahibi olan bir düşman, tohumu ve sonraki sonuçları kolayca tahmin edemez, bu da onları öyle görünmüyor. sonuçta rastgele. Bu yüzden bunu kriptografi için kullanmayı düşünme bile.
Süslü aşırı mühendislik rotasına ve C ++ 11'lere gidebilirsiniz std::uniform_int_distribution
sınıfına uygun bir rastgele cihazla ve her zaman popüler olan Mersenne twister gibi en sevdiğiniz rastgele motorlastd::mt19937
dört yaşındaki kuzeninizle zar atabilirsiniz, ancak bu bile gitmeyecek kriptografik anahtar materyali oluşturmaya uygun olun - ve Mersenne twister, müstehcen bir kurulum süresiyle CPU'nuzun önbelleğine zarar veren çok kilobaytlık bir durumla korkunç bir uzay domuzudur, bu nedenle, örneğin , paralel Monte Carlo simülasyonları için bile kötüdür. tekrarlanabilir alt hesaplama ağaçları; popülaritesi büyük olasılıkla akılda kalıcı adından kaynaklanmaktadır. Ancak bu örnekte olduğu gibi oyuncak zar atmak için kullanabilirsiniz!
Diğer bir yaklaşım, basit bir hızlı anahtar silme PRNG'si gibi küçük bir duruma sahip basit bir şifreleme sözde rasgele sayı üreteci veya kendinize güveniyorsanız sadece AES-CTR veya ChaCha20 gibi bir akış şifresi kullanmaktır ( örneğin , bir Monte Carlo simülasyonunda doğa bilimlerinde araştırma), devletin tehlikeye atılması durumunda geçmiş sonuçları tahmin etmenin olumsuz sonuçlarının olmadığı.
std::uniform_int_distribution