Bu, daha önce gönderilen bir sorunun devamı niteliğindedir:
C'de rastgele bir sayı nasıl oluşturulur?
Bir kalıbın kenarlarını taklit etmek için 1 ila 6 gibi belirli bir aralıktan rastgele bir sayı üretebilmek istiyorum.
Bunu nasıl yapacağım?
Bu, daha önce gönderilen bir sorunun devamı niteliğindedir:
C'de rastgele bir sayı nasıl oluşturulur?
Bir kalıbın kenarlarını taklit etmek için 1 ila 6 gibi belirli bir aralıktan rastgele bir sayı üretebilmek istiyorum.
Bunu nasıl yapacağım?
Yanıtlar:
Şimdiye kadarki tüm cevaplar matematiksel olarak yanlış. Dönen rand() % N
homojen aralığında bir sayı vermez [0, N)
sürece N
aralığının uzunluğu içine böler rand()
döner (yani bir 2 gücü). Dahası, modüllerinin rand()
bağımsız olup olmadığına dair hiçbir fikri yoktur : gitmeleri mümkündür 0, 1, 2, ...
, bu tekdüze fakat çok rastgele değildir. Yapılması mantıklı görünen tek varsayım, rand()
bir Poisson dağılımı ortaya koymasıdır: aynı büyüklükteki herhangi iki örtüşmeyen alt aralık eşit derecede olası ve bağımsızdır. Sonlu bir değerler kümesi için bu, tek tip bir dağılımı ifade eder ve aynı zamanda değerlerinin rand()
güzel bir şekilde dağılmasını sağlar.
Bu, aralığını değiştirmenin tek doğru yolunun rand()
onu kutulara bölmek olduğu anlamına gelir ; örneğin, RAND_MAX == 11
bir aralık istiyorsanız ve isterseniz 1..6
, {0,1}
1'e, {2,3}
2'ye vb. atamalısınız . Bunlar ayrık, eşit büyüklükte aralıklardır ve bu nedenle tek tip ve bağımsız olarak dağıtılır.
Kayan nokta bölmesini kullanma önerisi matematiksel olarak makuldür, ancak prensipte yuvarlama sorunlarından muzdariptir. Belki de double
çalışmasını sağlamak için yeterince yüksek hassasiyettir; belki de değil. Bilmiyorum ve çözmek zorunda kalmak istemiyorum; her durumda, cevap sisteme bağlıdır.
Doğru yol, tamsayı aritmetiği kullanmaktır. Yani, aşağıdaki gibi bir şey istiyorsunuz:
#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;
}
Döngü, mükemmel bir şekilde homojen bir dağılım elde etmek için gereklidir. Örneğin, size 0'dan 2'ye rastgele sayılar verilirse ve yalnızca 0'dan 1'e kadar sayılar istiyorsanız, 2 elde edemeyene kadar çekmeye devam edin; Bunun eşit olasılıkla 0 veya 1 verdiğini kontrol etmek zor değildir. Bu yöntem, farklı kodlanmış olsa da, hayırların yanıtlarında verdikleri bağlantıda da açıklanmaktadır. Daha iyi bir dağılıma sahip random()
olduğundan daha çok kullanıyorum rand()
(man sayfasında belirtildiği gibi rand()
).
Varsayılan aralığın dışında rastgele değerler almak istiyorsanız [0, RAND_MAX]
, o zaman zor bir şey yapmanız gerekir. Belki de en uygun bir işlev tanımlamaktır random_extended()
çeker n
bit (kullanarak random_at_most()
olarak) ve döner [0, 2**n)
, ve daha sonra uygulama random_at_most()
ile random_extended()
yerine random()
(ve 2**n - 1
yerine RAND_MAX
) daha az bir rasgele değerini çekmek için 2**n
bu tür tutabilecek bir sayısal türü olduğu varsayılarak bir değer. Son olarak, tabii ki, sen de değerler elde edebilirsiniz [min, max]
kullanarak min + random_at_most(max - min)
negatif değerleri de dahil olmak üzere,.
max - min > RAND_MAX
yukarıda belirttiğim sorundan daha ciddi bir durumdur (örneğin VC ++ RAND_MAX
sadece 32767'ye sahiptir).
do {} while()
.
@ Ryan Reich'ın cevabını takiben, temizlenmiş versiyonumu sunacağımı düşündüm. İkinci sınır kontrolü için ilk sınır kontrolü gerekli değildir ve bunu yinelemeli değil yinelemeli yaptım. [Min, max], nerede max >= min
ve aralığındaki değerleri döndürür 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);
}
limit
bir int (ve isteğe bağlı olarak bucket
da) yaparak kolayca çözülebilir . DÜZENLEME: Teklif gönderdim ve düzenledim. RAND_MAX / range
INT_MAX
buckets * range
RAND_MAX
Bir aralığın maksimum ve minimum değerlerini biliyorsanız ve aralık arasında dahil sayılar oluşturmak istiyorsanız bir formül:
r = (rand() % (max + 1 - min)) + min
int
taşma max+1-min
.
unsigned int
randr(unsigned int min, unsigned int max)
{
double scaled = (double)rand()/RAND_MAX;
return (max - min +1)*scaled + min;
}
Diğer seçenekler için buraya bakın .
(((max-min+1)*rand())/RAND_MAX)+min
aynı dağılımı kolayca kullanabilir ve muhtemelen aynı dağılımı elde edebilirsiniz (RAND_MAX'ın int'e göre taşmaması için yeterince küçük olduğunu varsayarsak).
max + 1
olmadığını ya rand() == RAND_MAX
, ya rand()
çok yakın RAND_MAX
ve kayan nokta hataları nihai sonuç geçmiş itmek max + 1
. Güvende olmak için, iade etmeden önce sonucun aralık dahilinde olup olmadığını kontrol etmelisiniz.
RAND_MAX + 1.0
. Yine de bunun bir max + 1
geri dönüşü önlemek için yeterince iyi olduğundan emin değilim : özellikle, + min
sonunda max + 1
büyük rand () değerleri üretebilecek bir turu içeriyor . Bu yaklaşımı tamamen terk etmek ve tamsayı aritmetiği kullanmak daha güvenlidir.
RAND_MAX
değiştirilir RAND_MAX+1.0
Christoph anlaşılacağı gibi, o zaman ben bu şartıyla güvenli olduğuna inanıyoruz + min
tamsayı aritmetik kullanılarak yapılır: return (unsigned int)((max - min + 1) * scaled) + min
. (Belirgin olmayan) sebep varsayarak IEEE 754 aritmetik ve yuvarlak yarı-hatta, (ve ayrıca bu yani max - min + 1
, her zaman doğrudur çift tam olarak gösterilebilen, ama bu tipik bir makinede gerçek olacak) o x * scaled < x
için herhangi bir pozitif çift x
ve herhangi bir çift scaled
tatmin edici 0.0 <= scaled && scaled < 1.0
.
randr(0, UINT_MAX)
: her zaman 0 üretir.
Sadece yapmaz mısın:
srand(time(NULL));
int r = ( rand() % 6 ) + 1;
%
modül operatörüdür. Esasen, 6'ya böler ve kalanı 0'dan 5'e döndürür.
rand()
jeneratörün durumunun düşük sıralı bitlerini içeren herhangi bir yerde kullanımda olan bir libc göster (eğer bir LCG kullanıyorsa). Şimdiye kadar bir tane görmedim - hepsi (evet, RAND_MAX yalnızca 32767 olan MSVC dahil) düşük dereceli bitleri kaldırıyor . Modülüsün kullanılması başka nedenlerden ötürü önerilmez, yani dağılımı daha küçük sayılar lehine çarpıtır.
Önyargı sorununu anlayan ancak red temelli yöntemlerin öngörülemeyen çalışma zamanına dayanamayanlar için bu seri, [0, n-1]
aralıkta giderek daha az önyargılı bir rastgele tam sayı üretir :
r = n / 2;
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
r = (rand() * n + r) / (RAND_MAX + 1);
...
Bunu, yüksek hassasiyetli sabit noktalı rasgele i * log_2(RAND_MAX + 1)
bit sayısını (burada i
yineleme sayısıdır) sentezleyerek ve uzun bir çarpma işlemi gerçekleştirerek yapar n
.
Bit sayısı ile karşılaştırıldığında yeterince büyük olduğunda n
, önyargı ölçülemeyecek kadar küçük hale gelir.
Bu eğer önemli değil RAND_MAX + 1
daha azdır n
(olduğu gibi bu soruya ), ya da ikisinin bir güç değilse, ancak eğer bakım önlemek tamsayı taşmasına karşı alınması gereken RAND_MAX * n
büyüktür.
RAND_MAX
sık sık INT_MAX
, yani RAND_MAX + 1
-> UB (INT_MIN gibi)
RAND_MAX * n
büyükse tamsayı taşmasını önlemek için özen gösterilmelidir" derken bunu kastediyorum . Gereksinimlerinize uygun türleri kullanmak için düzenlemeniz gerekir.
RAND_MAX
genellikle INT_MAX
" Evet, ancak yalnızca 16 bit sistemlerde! Herhangi bir makul modern mimari INT_MAX
2 ^ 32/2 ve RAND_MAX
2 ^ 16 / 2'yi koyacaktır . Bu yanlış bir varsayım mı?
int
derleyiciyi test ettim RAND_MAX == 32767
, birinde ve RAND_MAX == 2147483647
diğerinde buldum . Genel deneyimim (onlarca yıllık) bu RAND_MAX == INT_MAX
kadar sık. Yani makul modern 32 bit mimarisi kesinlikle sahip olacağını katılmıyorum RAND_MAX
at 2^16 / 2
. C spesifikasyonu izin verdiğinden 32767 <= RAND_MAX <= INT_MAX
, bir eğilimden çok buna kodluyorum.
Modulo önyargısından kaçınmak için (diğer cevaplarda önerilmektedir) her zaman şunları kullanabilirsiniz:
arc4random_uniform(MAX-MIN)+MIN
"MAX" üst sınırdır ve "MIN" alt sınırdır. Örneğin, 10 ile 20 arasındaki sayılar için:
arc4random_uniform(20-10)+10
arc4random_uniform(10)+10
Basit bir çözüm ve "rand ()% N" kullanmaktan daha iyi.
#include <bsd/stdlib.h>
Öncelikle yapmanız gerektiğini belirtmek gerekir . Ayrıca, bunu MinGW veya CygWin olmadan Windows'ta nasıl edineceğiniz hakkında bir fikriniz var mı?
İşte Ryan Reich'in çözümünden biraz daha basit bir algoritma:
/// 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
RAND_MAX + 1
kolayca int
eklenebilir. Bu durumda (RAND_MAX + 1) % range
şüpheli sonuçlar doğuracaktır. Consider(RAND_MAX + (uint32_t)1)
Ryan haklı olsa da, rastgeleliğin kaynağı hakkında bilinenlere dayanarak çözüm çok daha basit olabilir. Sorunu yeniden ifade etmek için:
[0, MAX)
Düzgün dağılımlı .[rmin, rmax]
nerede olduğu aralıkta tekdüze dağıtılmış rastgele tam sayılar üretmektir 0 <= rmin < rmax < MAX
.Tecrübelerime göre, bölmelerin (veya "kutuların") sayısı orijinal sayıların aralığından önemli ölçüde daha küçükse ve orijinal kaynak kriptografik olarak güçlü ise - tüm bu rigamarolü gözden geçirmeye gerek yoktur ve basit modulo bölme yeterli ( output = rnd.next() % (rmax+1)
eğer rmin == 0
, gibi ) ve eşit olarak "yeterince" ve hız kaybı olmadan dağıtılan rastgele sayılar üretir. Anahtar faktör rastgelelik kaynağıdır (yani çocuklar, bunu evderand()
).
İşte pratikte nasıl çalıştığına dair bir örnek / kanıt. Rastgele bayt üreten kriptografik olarak güçlü bir kaynağa sahip (Intel RDRAND tabanlı) 1'den 22'ye kadar rastgele sayılar üretmek istedim. Sonuçlar:
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%
Bu, amacım için ihtiyaç duyduğum üniformaya yakın (adil zar atma, http://users.telenet.be/d.rijmenants/en/kl-7sim.htm gibi 2.Dünya Savaşı şifre makineleri için kriptografik olarak güçlü kod kitapları oluşturarak) , vb ). Çıktı, kayda değer bir önyargı göstermiyor.
İşte şifreleme açısından güçlü (gerçek) rasgele sayı üretecinin kaynağı: Intel Dijital Rastgele Sayı Oluşturucu ve 64 bitlik (işaretsiz) rasgele sayılar üreten örnek bir kod.
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;
}
Mac OS X'te clang-6.0.1 (düz) ve gcc-4.8.3 ile "-Wa, q" işaretini kullanarak derledim (çünkü GAS bu yeni talimatları desteklemiyor).
gcc randu.c -o randu -Wa,q
(Ubuntu 16 GCC 5.3.1) ya da clang randu.c -o randu
(Clang 3.8.0) çalışır, ancak ile zamanında çekirdek dökümlerini Illegal instruction (core dumped)
. Herhangi bir fikir?
Daha önce söylendiği gibi, modulo, dağılımı çarpıttığı için yeterli değildir. İşte bitleri maskeleyen ve dağıtımın çarpık olmamasını sağlamak için onları kullanan kodum.
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;
}
}
}
Aşağıdaki basit kod, dağıtıma bakmanıza izin verir:
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]);
}
}
v = rand(); if (v > RAND_MAX - (RAND_MAX % range) -> reject and try again; else return v % range;
Modulo'nun maskelemeden çok daha yavaş bir işlem olduğunu anlıyorum, ama yine de ..... test edilmesi gerektiğini düşünüyorum.
rand()
int
aralıkta bir döndürür [0..RAND_MAX]
. Bu aralık, kolayca bir alt aralığı olabilir uint32_t
ve randomInRange(0, ,b)
bu aralıkta asla değer üretmez (INT_MAX...b]
.