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() % Nhomojen aralığında bir sayı vermez [0, N)sürece Naralığı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 == 11bir 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 nbit (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 - 1yerine RAND_MAX) daha az bir rasgele değerini çekmek için 2**nbu 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_MAXyukarıda belirttiğim sorundan daha ciddi bir durumdur (örneğin VC ++ RAND_MAXsadece 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 >= minve 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);
}
limitbir int (ve isteğe bağlı olarak bucketda) yaparak kolayca çözülebilir . DÜZENLEME: Teklif gönderdim ve düzenledim. RAND_MAX / rangeINT_MAXbuckets * rangeRAND_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
inttaş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)+minaynı 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 + 1olmadığını ya rand() == RAND_MAX, ya rand()çok yakın RAND_MAXve 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 + 1geri dönüşü önlemek için yeterince iyi olduğundan emin değilim : özellikle, + minsonunda max + 1bü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_MAXdeğiştirilir RAND_MAX+1.0Christoph anlaşılacağı gibi, o zaman ben bu şartıyla güvenli olduğuna inanıyoruz + mintamsayı 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 < xiçin herhangi bir pozitif çift xve herhangi bir çift scaledtatmin 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 iyineleme 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 + 1daha 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 * nbüyüktür.
RAND_MAXsık sık INT_MAX, yani RAND_MAX + 1-> UB (INT_MIN gibi)
RAND_MAX * nbü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_MAXgenellikle INT_MAX" Evet, ancak yalnızca 16 bit sistemlerde! Herhangi bir makul modern mimari INT_MAX2 ^ 32/2 ve RAND_MAX2 ^ 16 / 2'yi koyacaktır . Bu yanlış bir varsayım mı?
intderleyiciyi test ettim RAND_MAX == 32767, birinde ve RAND_MAX == 2147483647diğerinde buldum . Genel deneyimim (onlarca yıllık) bu RAND_MAX == INT_MAXkadar sık. Yani makul modern 32 bit mimarisi kesinlikle sahip olacağını katılmıyorum RAND_MAXat 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 + 1kolayca inteklenebilir. 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()intaralıkta bir döndürür [0..RAND_MAX]. Bu aralık, kolayca bir alt aralığı olabilir uint32_tve randomInRange(0, ,b)bu aralıkta asla değer üretmez (INT_MAX...b].