İnsanlar neden rasgele sayı üreteci kullanırken modulo önyargı olduğunu söylüyor?


277

Bu sorunun çok sorulduğunu gördüm, ancak buna kesin bir cevap görmedim. Bu yüzden burada insanların bir rand()C ++ gibi rastgele bir sayı üreteci kullanırken neden tam olarak "modulo önyargı" olduğunu anlamalarına yardımcı olacak bir mesaj göndereceğim .

Yanıtlar:


394

Yani rand()0 ile arasında RAND_MAXtanımlanmış bir sabit olan doğal bir sayı seçen sözde rastgele bir sayı üreteci de cstdlib( genel bir bakış için bu makaleye bakın rand()).

Şimdi 0 ile 2 arasında rasgele bir sayı oluşturmak isterseniz ne olur? Açıklama uğruna, diyelim ki RAND_MAX10'dur ve arayarak 0 ile 2 arasında rastgele bir sayı üretmeye karar veririm rand()%3. Ancak, rand()%3eşit olasılıkla 0 ile 2 arasındaki sayıları üretmez!

Zaman rand()döner 0, 3, 6 veya 9, rand()%3 == 0 . Bu nedenle, P (0) = 4/11

Zaman rand()döner 1, 4, 7 ya da 10, rand()%3 == 1 . Bu nedenle, P (1) = 4/11

Zaman rand()2, 5 ya da 8 döndürür rand()%3 == 2 . Bu nedenle, P (2) = 3/11

Bu, eşit olasılıkla 0 ile 2 arasında sayılar üretmez. Tabii ki küçük aralıklar için bu en büyük sorun olmayabilir, ancak daha büyük bir aralık için bu, daha küçük sayılara ağırlık vererek dağılımı çarpabilir.

Peki rand()%n, eşit olasılıkla 0'dan n-1'e kadar bir sayı aralığı ne zaman döndürür? Ne zaman RAND_MAX%n == n - 1. Bu durumda, önceki varsayımımızla birlikte rand()0 RAND_MAXile eşit olasılık arasında bir sayı döndürür , n'nin modulo sınıfları da eşit olarak dağıtılır.

Peki bu sorunu nasıl çözeriz? Ham bir yol, istediğiniz aralıkta bir sayı alana kadar rastgele sayılar üretmeye devam etmektir:

int x; 
do {
    x = rand();
} while (x >= n);

ancak aralığınızda bir değer elde etme şansınız nolduğundan düşük ortalama değerleri için verimsizdir n/RAND_MAXve bu nedenle ortalama olarak RAND_MAX/narama yapmanız gerekir rand().

Daha verimli bir formül yaklaşımı, uzunlukta bölünebilir bir uzunluk elde edene kadar rastgele sayılar üretmeye devam etmek ngibi bölünebilir bir uzunluğa sahip bazı geniş bir aralığı RAND_MAX - RAND_MAX % nalmak ve daha sonra modülü almak olacaktır:

int x;

do {
    x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));

x %= n;

Küçük değerleri için n, bu nadiren birden fazla çağrı gerektirir rand().


Alıntılanan çalışmalar ve daha fazla okuma:



6
Düşünmenin başka bir yolu da RAND_MAX%n == n - 1_ _ (RAND_MAX + 1) % n == 0. Kodu okurken, % something == 0diğer eşit hesaplama yöntemlerinden daha kolay “eşit bölünebilir” olarak anlamaya meyilliyim . Tabii ki, C ++ stdlib RAND_MAXile aynı değere sahipse INT_MAX, (RAND_MAX + 1)kesinlikle işe yaramaz; Mark'ın hesaplaması en güvenli uygulama olmaya devam ediyor.
Slipp D.Thompson

çok güzel bir cevap!
Sayali Sonawane

Nitpicking olabilirim, ancak amaç boşa giden bitleri azaltmaksa, RAND_MAX (RM) 'nin N tarafından eşit bölünebilir olmaktan sadece 1 daha az olduğu kenar durumu için bunu biraz iyileştirebiliriz. Bu senaryoda, bitlerin boşa harcanmasına gerek yoktur X> = (RM - RM% N)) yapın, bu da N'nin küçük değerleri için küçük bir değere sahiptir, ancak büyük N değerleri için daha büyük bir değere dönüşür. Slipp D. Thompson tarafından belirtildiği gibi, sadece işe yarayacak bir çözüm var INT_MAX (IM)> RAND_MAX olduğunda, eşit olduklarında kırılır. Bununla birlikte, bunun için X> = (RM - RM% N) hesaplamasını aşağıdaki gibi değiştirebiliriz:
Ben Personick

X> = RM - (((RM% N) + 1)% N)
Ben Personick

Sorunu ayrıntılı olarak açıklayan ve örnek kod çözümü veren ek bir cevap yayınladım.
Ben Personick

36

Rastgele seçmeye devam etmek, önyargıyı kaldırmak için iyi bir yoldur.

Güncelleme

Bir x aralığında bölünebilir ile arama yaparsak kodu hızlı hale getirebiliriz n.

// Assumptions
// rand() in [0, RAND_MAX]
// n in (0, RAND_MAX]

int x; 

// Keep searching for an x in a range divisible by n 
do {
    x = rand();
} while (x >= RAND_MAX - (RAND_MAX % n)) 

x %= n;

Yukarıdaki döngü çok hızlı olmalı, ortalama 1 yineleme.


2
Yuck :-P bir çifte dönüştürme, sonra MAX_UPPER_LIMIT / RAND_MAX ile çarpma çok daha temiz ve daha iyi performans gösterir.
boycy

22
@boycy: noktayı kaçırdınız. Dönebilen değerlerin sayısı rand()birden fazla değilse n, ne yaparsanız yapın, bu değerlerden bazılarını atmazsanız kaçınılmaz olarak 'modulo önyargı' alırsınız. user1413793 bunu güzel bir şekilde açıklar (bu cevapta önerilen çözüm gerçekten yucky olmasına rağmen).
TonyK

4
@TonyK özür dilerim, noktayı kaçırdım. Yeterince fazla düşünmedim ve yanlılığın sadece açık bir modül operasyonu kullanan yöntemlerle uygulanacağını düşündüm. Beni
düzelttiğiniz

Operatör önceliği RAND_MAX+1 - (RAND_MAX+1) % ndoğru çalışır, ancak yine de RAND_MAX+1 - ((RAND_MAX+1) % n)netlik için yazılması gerektiğini düşünüyorum .
Linus Arver

4
Bu, RAND_MAX == INT_MAX (çoğu sistemde olduğu gibi) çalışmaz . Yukarıdaki @ user1413793 için ikinci yorumuma bakın.
BlueRaja - Danny Pflughoeft

19

@ user1413793 sorun hakkında doğru. Bunu daha fazla tartışmayacağım, bir noktaya değinmek dışında: evet, küçük değerleri nve büyük değerleri için RAND_MAX, modulo önyargısı çok küçük olabilir. Ancak, önyargı oluşturucu bir desen kullanmak, rastgele bir sayı hesapladığınızda ve farklı durumlar için farklı desenler seçtiğinizde önyargıyı dikkate almanız gerektiği anlamına gelir. Ve yanlış bir seçim yaparsanız, tanıttığı hatalar ince ve birim testi yapmak neredeyse imkansızdır. Sadece uygun aracı (örneğin arc4random_uniform) kullanmakla karşılaştırıldığında , bu ekstra iştir, daha az iş değildir. Daha fazla iş yapmak ve daha kötü bir çözüm elde etmek korkunç bir mühendisliktir, özellikle de çoğu platformda her seferinde doğru yapmak kolaydır.

Ne yazık ki, çözümün uygulamaları olması gerekenden yanlış veya daha az verimlidir. (Her çözümün sorunları açıklayan çeşitli yorumları vardır, ancak çözümlerin hiçbiri bunları ele almak için düzeltilmemiştir.) Bu, sıradan cevap arayanı şaşırtacak gibi görünüyor, bu yüzden burada iyi bilinen bir uygulama sağlıyorum.

Yine, en iyi çözüm sadece arc4random_uniformonu sağlayan platformlarda veya platformunuz için benzer bir aralıklı çözümde ( Random.nextIntJava gibi ) kullanmaktır. Size hiçbir kod maliyeti olmadan doğru olanı yapacak. Bu neredeyse her zaman doğru çağrıdır.

Eğer yoksa arc4random_uniform, daha geniş ar4randombir RNG'nin üstüne nasıl uygulandığını görmek için açık kaynak gücünü kullanabilirsiniz ( bu durumda, ancak benzer bir yaklaşım diğer RNG'lerin üstünde de işe yarayabilir).

İşte OpenBSD uygulaması :

/*
 * Calculate a uniformly distributed random number less than upper_bound
 * avoiding "modulo bias".
 *
 * Uniformity is achieved by generating new random numbers until the one
 * returned is outside the range [0, 2**32 % upper_bound).  This
 * guarantees the selected random number will be inside
 * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
 * after reduction modulo upper_bound.
 */
u_int32_t
arc4random_uniform(u_int32_t upper_bound)
{
    u_int32_t r, min;

    if (upper_bound < 2)
        return 0;

    /* 2**32 % x == (2**32 - x) % x */
    min = -upper_bound % upper_bound;

    /*
     * This could theoretically loop forever but each retry has
     * p > 0.5 (worst case, usually far better) of selecting a
     * number inside the range we need, so it should rarely need
     * to re-roll.
     */
    for (;;) {
        r = arc4random();
        if (r >= min)
            break;
    }

    return r % upper_bound;
}

Benzer şeyleri uygulaması gerekenler için bu koddaki en son taahhüt yorumuna dikkat etmek gerekir:

Hesapla değiştirin arc4random_uniform () 2**32 % upper_boundolarak -upper_bound % upper_bound. Kodu sadeleştirir ve hem ILP32 hem de LP64 mimarileri için aynı, LP64 mimarileri üzerinde ise 64 bitlik bir kalıntı yerine 32 bitlik bir kalıntı kullanarak biraz daha hızlı yapar.

Jorden Verwer tarafından tech @ ok deraadt üzerinde; djm veya otto'dan itiraz yok

Java uygulaması da kolayca bulunabilir (önceki bağlantıya bakın):

public int nextInt(int n) {
   if (n <= 0)
     throw new IllegalArgumentException("n must be positive");

   if ((n & -n) == n)  // i.e., n is a power of 2
     return (int)((n * (long)next(31)) >> 31);

   int bits, val;
   do {
       bits = next(31);
       val = bits % n;
   } while (bits - val + (n-1) < 0);
   return val;
 }

Eğer unutmayın arcfour_random() aslında kendi alanlarında gerçek RC4 algoritmasını kullanır, çıktı kesinlikle bazı önyargı olacaktır. Umarım kütüphane yazarlarınız aynı arayüzün arkasında daha iyi bir CSPRNG kullanmaya başlamışlardır. BSD'lerden birinin şimdi uygulamak için ChaCha20 algoritmasını kullandığını hatırlıyorum arcfour_random(). Güvenlik veya video poker gibi diğer kritik uygulamalar için işe yaramaz hale getiren RC4 çıkış önyargıları hakkında daha fazla bilgi: blog.cryptographyengineering.com/2013/03/…
rmalayter

2
@rmalayter iOS ve OS X'te arc4random, sistemdeki en yüksek kalitede entropi olan / dev / random'dan okur. (Adındaki "arc4" tarihi ve uyumluluk açısından korunmuştur.)
Rob Napier

@Rob_Napier bilmek güzel, ama /dev/randomaynı zamanda geçmişte bazı platformlarda RC4 kullandı (Linux sayaç modunda SHA-1 kullanıyor). Ne yazık ki arama yoluyla bulduğum man sayfaları, RC4'ün çeşitli platformlarda hala kullanıldığını gösteriyor arc4random(gerçek kod farklı olabilir).
rmalayter

1
Kafam karıştı. Değil mi -upper_bound % upper_bound == 0??
Jon McClung

1
32 bitten daha genişse @JonMcClung -upper_bound % upper_boundgerçekten 0 olacaktır int. Bu olmalıdır (u_int32_t)-upper_bound % upper_bound)(varsayarak u_int32_tBSD izm içindir uint32_t).
Ian Abbott

14

Tanım

Modulo Bias , bir çıktı setini girdi setinin bir alt kümesine indirmek için modulo aritmetiği kullanımının doğasında var olan önyargıdır. Genel olarak, giriş ve çıkış seti arasındaki eşleme eşit olarak dağıtılmadığında, çıkış setinin boyutu giriş setinin boyutunun bir böleni olmadığında olduğu gibi, bir yanlılık vardır.

Bu yanlılığın özellikle sayıların bit dizeleri olarak gösterildiği bilgisayarlarda kaçınılmazdır: 0s ve 1s. Gerçekten rastgele rastgelelik kaynakları bulmak da son derece zordur, ancak bu tartışmanın kapsamı dışındadır. Bu cevabın geri kalanında sınırsız bir gerçekten rastgele bit kaynağı olduğunu varsayın.

Sorun Örneği

Bu rastgele bitleri kullanarak bir kalıp silindiri (0 ila 5) simüle etmeyi düşünelim. 6 olasılık var, bu yüzden 6 sayısını temsil etmek için yeterli bite ihtiyacımız var, bu da 3 bit. Ne yazık ki, 3 rastgele bit 8 olası sonuç verir:

000 = 0, 001 = 1, 010 = 2, 011 = 3
100 = 4, 101 = 5, 110 = 6, 111 = 7

Modulo 6 değerini alarak tam olarak 6'ya ayarlanan sonucun boyutunu azaltabiliriz, ancak bu modulo önyargı sorununu ortaya çıkarır : 1100 111verir ve 1 verir. Bu kalıp yüklenir.

Potansiyel çözümler

Yaklaşım 0:

Rasgele bitlere güvenmek yerine, teoride, bütün gün zar atmak ve sonuçları bir veritabanına kaydetmek için küçük bir ordu kiralayabilir ve daha sonra her bir sonucu sadece bir kez kullanabilirsiniz. Bu, göründüğü kadar pratiktir ve muhtemelen daha fazla gerçekten rastgele sonuçlar vermeyecektir (pun amaçlı).

Yaklaşım 1:

Bunun yerine modülü kullanarak, bir saf ama matematiksel doğru çözüm atılan sonuçlar bu Verim 110ve 111ve sadece 3 yeni bit ile yeniden deneyin. Ne yazık ki, bu , her ruloda, her ruloun kendileri de dahil olmak üzere, bir yeniden ruloya ihtiyaç duyma şansının% 25 olduğu anlamına gelir . Bu, en önemsiz kullanımlar dışında herkes için açıkça pratik değildir.

Yaklaşım 2:

Daha fazla bit kullanın: 3 bit yerine 4 kullanın. Bu, 16 olası sonuç verir. Tabii ki, sonuç 5'ten büyük olduğunda yeniden yuvarlama işleri daha da kötüleştirir (10/16 =% 62,5), böylece tek başına yardımcı olmaz.

2 * 6 = 12 <16 olduğuna dikkat edin, böylece 12'den daha az herhangi bir sonucu güvenle alabiliriz ve sonuçları eşit olarak dağıtmak için bu modulo 6'yı azaltabiliriz. Diğer 4 sonuç atılmalı ve daha sonra önceki yaklaşımda olduğu gibi tekrar yuvarlanmalıdır.

İlk başta kulağa hoş geliyor, ama matematiği kontrol edelim:

4 discarded results / 16 possibilities = 25%

Bu durumda, 1 ekstra bit hiç yardımcı olmadı !

Bu sonuç talihsiz, ancak 5 bit ile tekrar deneyelim:

32 % 6 = 2 discarded results; and
2 discarded results / 32 possibilities = 6.25%

Kesin bir gelişme, ancak birçok pratik durumda yeterince iyi değil. İyi haber şu ki, daha fazla bit eklemek asla atma ve yeniden yuvarlanma şansını artırmayacak . Bu sadece zar için değil, her durumda geçerlidir.

Ancak gösterildiği gibi , 1 ekstra bit eklemek hiçbir şeyi değiştirmeyebilir. Aslında rulonuzu 6 bite çıkarırsak, olasılık% 6.25 olarak kalır.

Bu 2 ek soru için yalvarır:

  1. Yeterli bit eklersek, atma olasılığının azalacağına dair bir garanti var mı?
  2. Genel durumda kaç bit yeterlidir ?

Genel Çözüm

Neyse ki ilk sorunun cevabı evet. 6 ile ilgili sorun, 2 ^ x mod 6'nın birbiri ile 2'nin katları olan 2 ve 4 arasında dönmesi, böylece çift x> 1 için,

[2^x mod 6] / 2^x == [2^(x+1) mod 6] / 2^(x+1)

Dolayısıyla 6 kuraldan ziyade bir istisnadır. Aynı şekilde 2 ardışık güç veren daha büyük modüller bulmak mümkündür, ancak sonuçta bu etrafa sarılmalı ve atılma olasılığı azalacaktır.

Daha fazla kanıt sunmadan , genellikle iki kat kullanılması gereken bit sayısının daha az, genellikle önemsiz bir şekilde atılma şansı sağlayacaktır.

Kavramın ispatı

Rasgele bayt sağlamak için OpenSSL'nin libcrypo'sunu kullanan örnek bir program. Derleme yaparken, -lcryptoherkesin kullanabileceği kütüphaneye bağlandığınızdan emin olun .

#include <iostream>
#include <assert.h>
#include <limits>
#include <openssl/rand.h>

volatile uint32_t dummy;
uint64_t discardCount;

uint32_t uniformRandomUint32(uint32_t upperBound)
{
    assert(RAND_status() == 1);
    uint64_t discard = (std::numeric_limits<uint64_t>::max() - upperBound) % upperBound;
    uint64_t randomPool = RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));

    while(randomPool > (std::numeric_limits<uint64_t>::max() - discard)) {
        RAND_bytes((uint8_t*)(&randomPool), sizeof(randomPool));
        ++discardCount;
    }

    return randomPool % upperBound;
}

int main() {
    discardCount = 0;

    const uint32_t MODULUS = (1ul << 31)-1;
    const uint32_t ROLLS = 10000000;

    for(uint32_t i = 0; i < ROLLS; ++i) {
        dummy = uniformRandomUint32(MODULUS);
    }
    std::cout << "Discard count = " << discardCount << std::endl;
}

Çoğu koşulda kaç tane yeniden oynatmanın olduğunu görmek için MODULUSve ROLLSdeğerleriyle oynamayı teşvik ediyorum . Şüpheci bir kişi hesaplanan değerleri dosyaya kaydetmek ve dağılımın normal göründüğünü doğrulamak isteyebilir.


Umarım hiç kimse tek tip rastgele uygulamanızı körü körüne kopyalamamıştır. Bu randomPool = RAND_bytes(...)çizgi her zaman randomPool == 1iddiadan kaynaklanacaktır. Bu her zaman bir atma ve yeniden rulo ile sonuçlanır. Bence ayrı bir hatta beyan etmek istedin. Sonuç olarak, bu, RNG'nin 1her yineleme için geri dönmesine neden oldu .
Qix - MONICA

Açık olmak gerekirse, randomPoolher zaman 1OpenSSL belgelerineRAND_bytes() göre değerlendirecektir, çünkü RAND_status()iddia sayesinde her zaman başarılı olacaktır .
Qix - MONICA

9

Modulo kullanımı ile ilgili iki genel şikayet vardır.

  • biri tüm jeneratörler için geçerlidir. Bir limit durumda görmek daha kolaydır. Jeneratörünüz 2 olan (C standardıyla uyumlu olmayan) bir RAND_MAX'a sahipse ve değer olarak sadece 0 veya 1 istiyorsanız, modulo kullanmak 0 (jeneratör 0 ve 2 ürettiğinde) 1 üretin (jeneratör 1 ürettiğinde). Değerleri düşürmez bırakmaz bunun doğru olduğunu unutmayın, jeneratör değerlerinden istediğinize eşleme yaptığınız her ne olursa olsun, biri diğerinden iki kat daha sık gerçekleşir.

  • bazı tür jeneratörler, en azından parametrelerinin bazıları için, diğerinden daha az önemli bitlerine sahiptir, ancak ne yazık ki, bu parametrenin başka ilginç özellikleri vardır (bu, RAND_MAX'ı 2 güçten daha azına sahip olabilir). Sorun iyi bilinmektedir ve uzun bir süre için kütüphane uygulaması muhtemelen problemi önler (örneğin C standardındaki örnek rand () uygulaması bu tür bir jeneratörü kullanır, ancak 16 daha az önemli biti düşürür), ancak bazıları şikayet etmekten hoşlanır bunu ve kötü şansın olabilir

Gibi bir şey kullanmak

int alea(int n){ 
 assert (0 < n && n <= RAND_MAX); 
 int partSize = 
      n == RAND_MAX ? 1 : 1 + (RAND_MAX-n)/(n+1); 
 int maxUsefull = partSize * n + (partSize-1); 
 int draw; 
 do { 
   draw = rand(); 
 } while (draw > maxUsefull); 
 return draw/partSize; 
}

0 ile n arasında rastgele bir sayı oluşturmak her iki sorunu da önler (ve RAND_MAX == INT_MAX ile taşmayı önler)

BTW, C ++ 11 azaltmaya ve rand () dışındaki diğer jeneratöre standart yollar getirmiştir.


n == RAND_MAX? 1: (RAND_MAX-1) / (n + 1): Buradaki fikrin ilk önce RAND_MAX'ı N eşit sayfa boyutuna bölmek, sonra N içindeki sapmayı döndürmek olduğunu anlıyorum, ancak kodu tam olarak bu haritaya eşleyemiyorum.
2012'de

1
Saf sürüm (RAND_MAX + 1) / (n + 1) olmalıdır, çünkü n + 1 gruplarına bölünecek RAND_MAX + 1 değerleri vardır. RAND_MAX + 1 hesaplanırken taşmayı önlemek için, 1+ (RAND_MAX-n) / (n + 1) olarak dönüştürülebilir. N + 1 hesaplanırken taşmayı önlemek için n == RAND_MAX durumu ilk olarak kontrol edilir.
AProgrammer

+ artı, bölme yapmak rejenere sayılara kıyasla daha maliyetli görünüyor.
56'da zinking

4
Modulo almak ve bölmek aynı maliyete sahiptir. Bazı ISA'lar her zaman her ikisini de sağlayan tek bir talimat bile sağlar. Rejenere sayıların maliyeti n ve RAND_MAX'a bağlı olacaktır. N, RAND_MAX'a göre küçükse, çok maliyetli olabilir. Açıkçası, önyargıların uygulamanız için önemli olmadığına karar verebilirsiniz; Onlardan kaçınmak için bir yol veriyorum.
AProgrammer

9

Mark's Solution (Kabul edilen çözüm) Neredeyse Mükemmel.

int x;

do {
    x = rand();
} while (x >= (RAND_MAX - RAND_MAX % n));

x %= n;

25 Mart'ta saat 23: 16'da düzenlendi

Mark Amery 39k21170211

Bununla birlikte, herhangi bir senaryoda 1 geçerli sonuç kümesini atayan bir uyarı vardır; burada RAND_MAX( RM), ( ) sayısının katından 1 daha azdır N(Where N= olası geçerli sonuçların sayısı).

yani, 'atılan değer sayısı' ( D) eşit olduğunda N, bunlar aslında geçerli V)bir kümedir (geçersiz bir küme değil I).

Bunun nedenleri Mark arasındaki fark gözden kaybeder noktada olduğunu Nve Rand_Max.

Ngeçerli üyeleri bir dizi yanıt içerdiğinden, geçerli üyeleri yalnızca Pozitif Tamsayılardan oluşan bir kümedir. (örneğin: Set N= {1, 2, 3, ... n })

Rand_max Bununla birlikte (bizim amaçlarımız için tanımlandığı gibi) herhangi bir sayıda negatif olmayan tam sayı içeren bir kümedir.

En genel biçiminde, burada Rand Maxteorik olarak negatif sayılar veya sayısal olmayan değerler içerebilen tüm geçerli sonuçların Kümesi olarak tanımlanan şeydir .

Bu nedenle Rand_Max"Olası Yanıtlar" kümesi olarak daha iyi tanımlanır.

Bununla birlikte N, geçerli yanıtlar kümesindeki değerlerin sayımına karşı çalışır, bu nedenle özel durumumuzda tanımlandığı gibi, Rand_Maxiçerdiği toplam sayıdan daha az bir değer olacaktır.

Mark'ın Çözümü kullanılarak, Değerler şu durumlarda Atılır: X => RM - RM% N

EG: 

Ran Max Value (RM) = 255
Valid Outcome (N) = 4

When X => 252, Discarded values for X are: 252, 253, 254, 255

So, if Random Value Selected (X) = {252, 253, 254, 255}

Number of discarded Values (I) = RM % N + 1 == N

 IE:

 I = RM % N + 1
 I = 255 % 4 + 1
 I = 3 + 1
 I = 4

   X => ( RM - RM % N )
 255 => (255 - 255 % 4) 
 255 => (255 - 3)
 255 => (252)

 Discard Returns $True

Yukarıdaki örnekte de görebileceğiniz gibi, X değeri (başlangıç ​​işlevinden aldığımız rastgele sayı) 252, 253, 254 veya 255 olduğunda, bu dört değer geçerli bir döndürülen değerler kümesi içermesine rağmen onu atacağız .

IE: Atılan değerlerin sayısı (I) = N (Geçerli sonuçların sayısı) geçerli bir dönüş değeri kümesi orijinal işlev tarafından atılır.

N ve RM değerleri arasındaki farkı D olarak tanımlarsak, yani:

D = (RM - N)

Daha sonra D değeri küçüldükçe, bu yönteme bağlı olarak gereksiz yeniden silindirlerin yüzdesi her bir doğal çarpımda artar. (RAND_MAX bir Prime Numarasına DEĞİL DEĞİLSE, bu geçerli bir husustur)

ÖRNEĞİN:

RM=255 , N=2 Then: D = 253, Lost percentage = 0.78125%

RM=255 , N=4 Then: D = 251, Lost percentage = 1.5625%
RM=255 , N=8 Then: D = 247, Lost percentage = 3.125%
RM=255 , N=16 Then: D = 239, Lost percentage = 6.25%
RM=255 , N=32 Then: D = 223, Lost percentage = 12.5%
RM=255 , N=64 Then: D = 191, Lost percentage = 25%
RM=255 , N= 128 Then D = 127, Lost percentage = 50%

İhtiyaç duyulan Reroll'lerin yüzdesi N'nin RM'ye yaklaştıkça artması nedeniyle, bu kodu çalıştıran sistemin kısıtlamalarına ve aranan değerlere bağlı olarak birçok farklı değer için geçerli olabilir.

Bunu reddetmek için burada gösterildiği gibi basit bir değişiklik yapabiliriz:

 int x;

 do {
     x = rand();
 } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );

 x %= n;

Bu, formülün, maksimum değerlerinizi tanımlamak için modül kullanmanın ek özelliklerini açıklayan daha genel bir sürümünü sağlar.

N'nin çarpımı olan RAND_MAX için küçük bir değer kullanma örnekleri.

Mark'original Versiyon:

RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X >= (RAND_MAX - ( RAND_MAX % n ) )
When X >= 2 the value will be discarded, even though the set is valid.

Genelleştirilmiş Sürüm 1:

RAND_MAX = 3, n = 2, Values in RAND_MAX = 0,1,2,3, Valid Sets = 0,1 and 2,3.
When X > (RAND_MAX - ( ( RAND_MAX % n  ) + 1 ) % n )
When X > 3 the value would be discarded, but this is not a vlue in the set RAND_MAX so there will be no discard.

Ayrıca, N'nin RAND_MAX içindeki değerlerin sayısı olması durumunda; bu durumda, RAND_MAX = INT_MAX olmadığı sürece N = RAND_MAX +1 ayarlayabilirsiniz.

Ancak, sadece N = 1 kullanabilirsiniz ve X'in herhangi bir değeri kabul edilir ve son çarpanınız için bir IF ifadesi ekler. Ancak, işlev n = 1 ile çağrıldığında 1 döndürmek için geçerli bir nedeni olabilecek bir kodunuz olabilir ...

Bu nedenle, n = RAND_MAX + 1 olmasını istediğinizde, normalde bir Div 0 Hatası sağlayacak olan 0 kullanmak daha iyi olabilir.

Genelleştirilmiş Sürüm 2:

int x;

if n != 0 {
    do {
        x = rand();
    } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) );

    x %= n;
} else {
    x = rand();
}

Bu çözümlerin her ikisi de, RM + 1 bir n ürünü olduğunda ortaya çıkan gereksiz yere atılmış geçerli sonuçlarla sorunu çözmektedir.

İkinci sürüm ayrıca, RAND_MAX'ta bulunan toplam olası değer kümesine eşit olması gerektiğinde uç senaryo senaryosunu da kapsar.

Her ikisinde de değiştirilmiş yaklaşım aynıdır ve geçerli rastgele sayılar sağlama ve atılan değerleri en aza indirme ihtiyacına daha genel bir çözüm sağlar.

Tekrarlamak için:

Markanın örneğini genişleten Temel Genel Çözüm:

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

 int x;

 do {
     x = rand();
 } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );

 x %= n;

Bir ek RAND_MAX + 1 = n senaryosuna izin veren Genişletilmiş Genel Çözüm:

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

int x;

if n != 0 {
    do {
        x = rand();
    } while (x > (RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) ) );

    x %= n;
} else {
    x = rand();
}

Bazı dillerde (özellikle yorumlanan diller), karşılaştırma işleminin while koşulu dışında hesaplamaları yapmak, daha fazla yeniden deneme yapılması gerekmeksizin tek seferlik bir hesaplama olduğundan daha hızlı sonuçlara yol açabilir. YMMV!

// Assumes:
//  RAND_MAX is a globally defined constant, returned from the environment.
//  int n; // User input, or externally defined, number of valid choices.

int x; // Resulting random number
int y; // One-time calculation of the compare value for x

if n != 0 {
    y = RAND_MAX - ( ( ( RAND_MAX % n ) + 1 ) % n) 
    do {
        x = rand();
    } while (x > y);

    x %= n;
} else {
    x = rand();
}

Mark'ın çözümü ile ilgili sorunun RAND_MAX ve n'ye aslında iki farklı şey ifade ettiklerinde aynı "ölçü birimi" olarak davrandığını söylemek güvenli değil mi? N, sonuçta elde edilen "olasılık sayısını" temsil ederken, RAND_MAX yalnızca orijinal olasılıkların maksimum değerini temsil eder; burada RAND_MAX + 1, orijinal olasılık sayısıdır. N ve RAND_MAX'ın denklemle aynı şey olmadığı anlaşılan sonucuna ulaşmadığına şaşırdım:RAND_MAX%n = n - 1
Danilo Souza Morães

@ DaniloSouzaMorães Teşekkür ederim Danilo, Konuyu çok özlediniz. Neden ve nasıl olduğu ile birlikte ne yaptığını göstermeye gittim, ancak ne ve nasıl başarılı bir şekilde yanlış yaptığını hiç söyleyemediğimi sanmıyorum, çünkü nasıl ve neden bir sorun var, neyin söz konusu olduğunu açıkça belirtmiyorum. Cevabımı, burada yazdığınızlardan bazılarını, kabul edilen çözümün ne ve nerede yaptığı konusunda kendi özetim olarak kullanmak için zirveye yakın bir yerde ele alınması gerekenleri değiştirir miyim?
Ben Personick

Bu harika olurdu. Git
Danilo Souza Morães

1

Bir RAND_MAXdeğerle 3(gerçekte bundan çok daha yüksek olmalı, ancak önyargı hala var olacaktır) bu hesaplamalardan bir önyargı olduğu mantıklıdır:

1 % 2 = 1 2 % 2 = 0 3 % 2 = 1 random_between(1, 3) % 2 = more likely a 1

Bu durumda, % 2sen arasında rastgele bir sayı istediğinizde yapmaması gereken budur 0ve 1. Bu arada 0ve 2yaparak rastgele bir sayı elde edebilirsiniz % 3, çünkü bu durumda: RAND_MAXkatlarıdır 3.

Diğer yöntem

Çok daha basit ama diğer cevaplara eklemek için, burada önyargısız 0ve n - 1çok nfarklı olasılıklar arasında rastgele bir sayı elde etmek için benim çözümüm .

  • olasılık sayısını kodlamak için gereken bit (bayt değil) sayısı, ihtiyacınız olan rastgele verilerin bit sayısıdır
  • sayıyı rastgele bitlerden kodla
  • bu sayı ise >= n, yeniden başlatın (modulo yok).

Gerçekten rastgele verilerin elde edilmesi kolay değildir, bu yüzden neden gerekenden daha fazla bit kullanıyorsunuz?

Aşağıda Smalltalk'ta bir sözde rasgele sayı üretecinden bir bit önbellek kullanan bir örnek bulunmaktadır. Ben güvenlik uzmanı değilim, bu yüzden kendi sorumluluğunuzdadır kullanın.

next: n

    | bitSize r from to |
    n < 0 ifTrue: [^0 - (self next: 0 - n)].
    n = 0 ifTrue: [^nil].
    n = 1 ifTrue: [^0].
    cache isNil ifTrue: [cache := OrderedCollection new].
    cache size < (self randmax highBit) ifTrue: [
        Security.DSSRandom default next asByteArray do: [ :byte |
            (1 to: 8) do: [ :i |    cache add: (byte bitAt: i)]
        ]
    ].
    r := 0.
    bitSize := n highBit.
    to := cache size.
    from := to - bitSize + 1.
    (from to: to) do: [ :i |
        r := r bitAt: i - from + 1 put: (cache at: i)
    ].
    cache removeFrom: from to: to.
    r >= n ifTrue: [^self next: n].
    ^r

-1

Gibi kabul cevap gösterir "modülo önyargı" düşük değerde kökleri vardır RAND_MAX. RAND_MAXRAND_MAX 10 olsaydı,% kullanarak 0 ile 2 arasında bir sayı üretmeye çalıştığınızı göstermek için son derece küçük bir (10) değeri kullanır :

rand() % 3   // if RAND_MAX were only 10, gives
output of rand()   |   rand()%3
0                  |   0
1                  |   1
2                  |   2
3                  |   0
4                  |   1
5                  |   2
6                  |   0
7                  |   1
8                  |   2
9                  |   0

Yani 4 çıkış 0 (4/10 şans) ve sadece 3 çıkış 1 ve 2 (her biri 3/10 şans) vardır.

Bu yüzden önyargılı. Düşük rakamların ortaya çıkma şansı daha yüksektir.

Ama bu sadece gösterileri yukarı kadar açık olduğunda RAND_MAXküçüktür . Ya da daha spesifik olarak, modifikasyon yaptığınız sayı,RAND_MAX .

Döngüden çok daha iyi bir çözüm (delicesine verimsiz ve hatta önerilmemesi gerekir), çok daha geniş bir çıkış aralığına sahip bir PRNG kullanmaktır. Mersenne twister algoritması 4,294,967,295 maksimum çıkışı vardır. MersenneTwister::genrand_int32() % 10Tüm niyetler ve amaçlar için böyle yapmak eşit olarak dağıtılacak ve modulo önyargı etkisi tamamen ortadan kalkacaktır.


3
Sizinki daha verimlidir ve RAND_MAX, modifikasyon yaptığınız sayıdan önemli ölçüde daha büyükse, yine de sizinki yanlı olacaktır. Bunların hepsi yine de sahte rasgele sayı üreteçleri ve kendi başına farklı bir konudur, ancak tamamen rastgele bir sayı üreteci varsayarsanız, yolunuz hala düşük değerleri önyargılar.
user1413793

En yüksek değer garip MT::genrand_int32()%2olduğu için, zamanın% 0 (50 + 2,3e-8) ve% 1 (50 - 2,3e-8) oranını seçer. Bir kumarhanenin RGN'sini (muhtemelen çok daha geniş bir RGN aralığı kullanacaksınız) inşa etmedikçe, herhangi bir kullanıcı zamanın% 2,3-8'ini fark etmeyecektir. Burada önemli olmayacak kadar küçük sayılardan bahsediyorsun.
bobobobo

7
Döngü en iyi çözümdür. "Delicesine verimsiz" değildir; en kötü ortalama durumda yinelemenin iki katından daha azını gerektirir. Yüksek bir RAND_MAXdeğer kullanılması , modulo yanlılığını azaltır, ancak ortadan kaldırmaz. Döngü irade.
Jared Nielsen

5
Eğer RAND_MAXsen tarafından modding olan sayıdan daha yeterince büyüktür, kaç kez rastgele sayı yok denecek kadar azdır ve verimliliği etkilemez yenilemeye gerek yoktur. Ben kabul edilen cevap tarafından önerilen nyerine , en büyük katına karşı test sürece döngü tutmak diyorum n.
Mark Ransom

-3

Von Neumann'ın rastgele sayı oluşturma sürecinde teorik olarak önyargıları ortadan kaldırması gereken Tarafsız Para Çevirme Yöntemi için bir kod yazdım. Daha fazla bilgi ( http://en.wikipedia.org/wiki/Fair_coin ) adresinde bulunabilir.

int unbiased_random_bit() {    
    int x1, x2, prev;
    prev = 2;
    x1 = rand() % 2;
    x2 = rand() % 2;

    for (;; x1 = rand() % 2, x2 = rand() % 2)
    {
        if (x1 ^ x2)      // 01 -> 1, or 10 -> 0.
        {
            return x2;        
        }
        else if (x1 & x2)
        {
            if (!prev)    // 0011
                return 1;
            else
                prev = 1; // 1111 -> continue, bias unresolved
        }
        else
        {
            if (prev == 1)// 1100
                return 0;
            else          // 0000 -> continue, bias unresolved
                prev = 0;
        }
    }
}

Bu modulo önyargılarına değinmez. Bu işlem, bir bit akışındaki yanlılığı ortadan kaldırmak için kullanılabilir. Bununla birlikte, bir bit akımından 0'dan n'ye eşit bir dağılıma ulaşmak için, burada n, iki güçten daha az değildir, modulo önyargılarının ele alınmasını gerektirir. Böylece bu çözüm rastgele sayı üretme sürecindeki herhangi bir yanlılığı
Rick

2
@ Rick hmm. Von Neumann'ın, örneğin 1 ile 100 arasında rastgele bir sayı üretilirken modulo yanlılığını ortadan kaldırma yönteminin mantıksal uzantısı şöyle olacaktır: A) rand() % 100100 kez arayın . B) eğer tüm sonuçlar farklıysa, ilkini alın. C) aksi takdirde, GOTO A. Bu işe yarayacaktır, ancak beklenen sayıda yineleme yaklaşık 10 ^ 42 olduğunda, oldukça sabırlı olmanız gerekecektir. Ve ölümsüz.
Mark Amery

@MarkAmery Gerçekten işe yarayacak. Doğru bir şekilde uygulanmamış olmasına rağmen bu algoritmayı incelemek. İlk önce şöyle olmalı:else if(prev==2) prev= x1; else { if(prev!=x1) return prev; prev=2;}
Rick
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.