Random Sayı Üretimi — Modern C++ Yaklaşımı
Bilgisayarlar deterministik makinelerdir. Yani aynı girdiyi verirsen, aynı çıktıyı alırsın. O zaman "rastgele sayı" diye bir şey nasıl olabiliyor? Aslında olamıyor — bilgisayarların ürettiği sayılara sözde rastgele (pseudo-random) diyoruz. Ama iyi bir algoritmayla üretilmiş sözde rastgele sayılar, pratikte gerçek rastgelelikten ayırt edilemez.
Bu derste C++'ın eski ve yeni rastgele sayı üretim yöntemlerini karşılaştıracağız. Neden rand() fonksiyonundan uzak durman gerektiğini, modern <random> kütüphanesinin nasıl çalıştığını ve gerçek dünya örneklerini göreceğiz.
Eski Usul: rand() ve srand()
C'den miras kalan rand() fonksiyonu, belki de programlamaya yeni başlayanların ilk tanıştığı rastgele sayı üretme yöntemidir. Kullanımı basit görünür:
#include <cstdlib>
#include <ctime>
#include <iostream>
int main() {
srand(time(0)); // seed olarak şu anki zamanı kullan
for (int i = 0; i < 5; i++) {
int sayi = rand() % 100; // 0-99 arası
std::cout << sayi << " ";
}
// Örnek çıktı: 42 17 83 5 61
return 0;
}Basit, değil mi? İki satırda iş tamam. Ama bu basitliğin arkasında ciddi sorunlar var.
rand()'ın Sorunları
1. Düşük kaliteli rastgelelik: Çoğu implementasyonda rand() bir Linear Congruential Generator (LCG) kullanır. Bu algoritmanın periyodu (tekrar etmeden üretebileceği sayı miktarı) genellikle sadece 2^32 civarındadır. Daha kötüsü, düşük bitler (lower bits) çok kötü rastgelelik gösterir.
2. Modulo bias (mod yanlılığı): rand() % 6 yazarak 0-5 arası zar atmaya çalıştığında, sonuç eşit dağılmaz. RAND_MAX genelde 32767'dir. 32768 sayıyı 6'ya bölersen, bazı kalıntılar (remainder) diğerlerinden daha sık çıkar. Küçük aralıklarda fark etmez gibi görünür, ama Monte Carlo simülasyonu gibi hassas işlerde sonuçları çarpıtır.
3. Global state: rand() tek bir global değişkende durumunu tutar. Bu da demek oluyor ki:
İki farklı modül aynı
rand()'ı paylaşır — birinin çağrısı diğerinin dizisini bozar.Thread-safe değildir. İki thread aynı anda
rand()çağırırsa sonuç tanımsızdır.
4. Tekrarlanabilirlik zorluğu: Test için belirli bir seed ile aynı diziyi tekrar üretmek istersen, global state yüzünden programın başka yerlerindeki rand() çağrıları senin dizini bozabilir.
Analoji:
rand()fonksiyonunu eski bir radyo gibi düşün. Evet, müzik çalıyor, ama frekans kayıyor, parazit var, ve evin her odasından aynı radyoyu kontrol ediyorsun. Modern<random>kütüphanesi ise her oda için ayrı, dijital, kristal netliğinde bir müzik sistemi.
Modern C++: <random> Kütüphanesi
C++11 ile gelen <random> başlık dosyası, rastgele sayı üretimini engine + distribution (motor + dağılım) modeline oturttu. Bu iki parçalı model çok zarif:
Engine (motor): Ham rastgele bitler üretir. Hangi algoritmayı kullanacağını belirler.
Distribution (dağılım): Engine'den gelen ham bitleri istediğin aralığa ve dağılıma dönüştürür.
Bu ayrım sayesinde aynı engine'i farklı dağılımlarla, ya da aynı dağılımı farklı engine'lerle kullanabilirsin. LEGO gibi parçaları istediğin gibi birleştirirsin.
#include <random>
#include <iostream>
int main() {
// 1. Engine oluştur ve seed ver
std::random_device rd; // gerçek rastgele seed kaynağı
std::mt19937 engine(rd()); // Mersenne Twister engine
// 2. Dağılım tanımla
std::uniform_int_distribution<int> dist(1, 6); // 1-6 arası eşit dağılım
// 3. Sayı üret
for (int i = 0; i < 5; i++) {
std::cout << dist(engine) << " ";
}
// Örnek çıktı: 3 6 1 4 2
return 0;
}Üç adım: engine, dağılım, üretim. Bu kadar. Ve bu yaklaşım rand()'ın tüm sorunlarını çözer:
Modulo bias yok — dağılım nesnesi bunu doğru hesaplar.
Global state yok — her engine kendi durumunu tutar.
Thread-safe kullanılabilir — her thread kendi engine'ine sahip olabilir.
Engine'ler: Rastgeleliğin Motoru
C++ standart kütüphanesi birkaç farklı engine sunar. Hepsinin avantajları ve dezavantajları var.
std::mt19937 — Mersenne Twister
En yaygın kullanılan engine. Adını Mersenne asal sayısı 2^19937 - 1'den alır ve bu sayı aynı zamanda periyodudur. Evet, doğru okudun: tekrar etmeden 2^19937 - 1 farklı sayı üretebilir. Bu sayı, evrendeki atom sayısından bile çok büyük.
#include <random>
// 32-bit versiyonu (en yaygın)
std::mt19937 gen32(42); // 42 seed ile
// 64-bit versiyonu (daha geniş çıktı aralığı)
std::mt19937_64 gen64(42);Mersenne Twister'ın özellikleri:
Periyot: 2^19937 - 1 (astronomik)
State boyutu: 624 adet 32-bit integer (yaklaşık 2.5 KB)
Hız: Çok iyi — çoğu uygulama için yeterince hızlı
Kalite: İstatistiksel testlerin büyük çoğunluğunu geçer
Kriptografi: Uygun DEĞİL! Çıktıdan internal state tahmin edilebilir
Diğer Engine'ler
// Linear Congruential Generator — hafif, hızlı, düşük kalite
std::minstd_rand lcg_engine(42);
// Subtract-with-carry — orta kalite
std::ranlux24 ranlux_engine(42);
// 64-bit Mersenne Twister
std::mt19937_64 mt64_engine(42);Pratikte std::mt19937 neredeyse her zaman doğru seçimdir. Çok özel durumlar (gömülü sistemde bellek kısıtı, kriptografik gereksinim) yoksa bunu kullan.
Seed: Engine'i Başlatmak
Seed (tohum), engine'in başlangıç durumunu belirler. Aynı seed = aynı sayı dizisi. Bu hem avantaj hem risk:
Avantaj: Test ve debug için tekrarlanabilir sonuçlar üretebilirsin.
Risk: Kötü seed seçimi, kötü rastgelelik demek.
std::random_device — Donanım Tabanlı Seed
std::random_device işletim sisteminin entropi kaynağından (donanım gürültüsü, interrupt zamanlamaları vb.) gerçek rastgele sayılar üretir.
#include <random>
#include <iostream>
int main() {
std::random_device rd;
// Her çağrıda farklı bir değer
std::cout << rd() << std::endl;
std::cout << rd() << std::endl;
// Tipik kullanım: engine'e seed vermek
std::mt19937 engine(rd());
return 0;
}⚠️ Dikkat:
std::random_devicebazı platformlarda (özellikle MinGW gibi bazı Windows derleyicilerinde) deterministik olabilir — yani her çalıştırmada aynı değeri döndürür! Bu durumdarandom_deviceaslında rastgele değildir. Kodunu cross-platform yapıyorsan, bunu test et. Alternatif olarakrandom_deviceile birlikte zaman tabanlı seed'i de karıştırabilirsin.
Chrono-Based Seed — Zaman Tabanlı
Eski time(0) yerine daha hassas zaman damgası kullanabilirsin:
#include <random>
#include <chrono>
int main() {
// Nanosaniye hassasiyetinde zaman damgası
auto seed = std::chrono::high_resolution_clock::now()
.time_since_epoch()
.count();
std::mt19937 engine(seed);
// Veya random_device ile birleştir
std::random_device rd;
std::mt19937 engine2(rd() ^ seed); // XOR ile karıştır
return 0;
}Seed Sequence — Daha İyi Başlangıç
Mersenne Twister'ın 624 adet 32-bit state'i var, ama tek bir uint32_t seed ile sadece 2^32 farklı başlangıç durumu elde edersin. Daha iyi bir başlatma için std::seed_seq kullanabilirsin:
#include <random>
int main() {
std::random_device rd;
// Birden fazla rastgele değerle seed oluştur
std::seed_seq seed{rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()};
std::mt19937 engine(seed);
// Artık çok daha fazla olası başlangıç durumu var
return 0;
}💡 İpucu: Ciddi simülasyonlarda (Monte Carlo vb.) seed_seq ile en az 8 değer kullanarak seed oluştur. Günlük kullanımda tek bir rd() genellikle yeterlidir.
Dağılımlar: Sayıları Şekillendirmek
Engine ham bitler üretir. Dağılımlar bu bitleri anlamlı sayılara dönüştürür. C++'ın sunduğu dağılımlardan en önemlilerini görelim.
uniform_int_distribution — Eşit Dağılımlı Tam Sayı
En basit ve en çok kullanılan dağılım. Belirtilen aralıktaki her tam sayının eşit şansı var.
#include <random>
#include <iostream>
int main() {
std::mt19937 engine(42);
// [1, 100] aralığında eşit dağılımlı tam sayı
// NOT: Her iki uç da dahil!
std::uniform_int_distribution<int> dist(1, 100);
for (int i = 0; i < 10; i++) {
std::cout << dist(engine) << " ";
}
return 0;
}Dikkat: Aralık kapalı — yani hem a hem b dahil. dist(1, 6) → {1, 2, 3, 4, 5, 6}. Bu, rand() % 6'dan farklı çünkü modulo bias yok.
uniform_real_distribution — Eşit Dağılımlı Ondalıklı Sayı
Gerçel sayılar (floating-point) için eşit dağılım. Aralık yarı açık: [a, b).
#include <random>
#include <iostream>
#include <iomanip>
int main() {
std::mt19937 engine(42);
// [0.0, 1.0) aralığında double
std::uniform_real_distribution<double> dist(0.0, 1.0);
std::cout << std::fixed << std::setprecision(6);
for (int i = 0; i < 5; i++) {
std::cout << dist(engine) << std::endl;
}
return 0;
}normal_distribution — Normal (Gauss) Dağılım
Doğadaki pek çok olay normal dağılıma uyar: boy uzunluğu, IQ skorları, ölçüm hataları. Çan eğrisi (bell curve) şeklindedir.
#include <random>
#include <iostream>
#include <iomanip>
#include <map>
int main() {
std::mt19937 engine(42);
// Ortalama (mean) = 170, Standart sapma (stddev) = 10
// İnsan boy dağılımı gibi düşün (cm)
std::normal_distribution<double> boy_dist(170.0, 10.0);
// Historgram oluşturalım
std::map<int, int> histogram;
for (int i = 0; i < 10000; i++) {
int boy = static_cast<int>(std::round(boy_dist(engine)));
histogram[boy]++;
}
// Basit histogram çiz
for (auto& [boy, sayi] : histogram) {
if (boy >= 140 && boy <= 200) {
std::cout << boy << " | ";
std::cout << std::string(sayi / 20, '*') << std::endl;
}
}
return 0;
}Diğer Kullanışlı Dağılımlar
C++ standart kütüphanesi oldukça zengin dağılım seçenekleri sunar:
#include <random>
std::mt19937 engine(42);
// Bernoulli — yazı/tura gibi, true/false döndürür
std::bernoulli_distribution yazı_tura(0.5); // %50 true
bool sonuc = yazı_tura(engine);
// Poisson — nadir olayların sayısı (web sayfasına dakikada gelen istek)
std::poisson_distribution<int> istek_dist(4.0); // ortalama 4
int istek_sayisi = istek_dist(engine);
// Exponential — olaylar arası bekleme süresi
std::exponential_distribution<double> bekleme(1.0 / 5.0); // ortalama 5
double sure = bekleme(engine);
// Discrete — ağırlıklı seçim (farklı olasılıklar)
std::discrete_distribution<int> hileli_zar({1, 1, 1, 1, 1, 3}); // 6 daha sık
int zar = hileli_zar(engine); // 0-5 arası (6. yüz 3 kat olası)Pratik Örnek 1: Zar Atma Simülasyonu
Basit bir zar atma simülasyonu yapıp, sonuçların gerçekten eşit dağılıp dağılmadığını kontrol edelim.
#include <random>
#include <iostream>
#include <iomanip>
#include <array>
int main() {
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<int> zar(1, 6);
const int ATIS_SAYISI = 600000;
std::array<int, 7> sayac{}; // index 0 kullanılmayacak
// 600.000 kez zar at
for (int i = 0; i < ATIS_SAYISI; i++) {
int sonuc = zar(engine);
sayac[sonuc]++;
}
// Sonuçları göster — her yüz ~%16.67 olmalı
std::cout << std::fixed << std::setprecision(2);
for (int yuz = 1; yuz <= 6; yuz++) {
double yuzde = (sayac[yuz] * 100.0) / ATIS_SAYISI;
std::cout << "Yüz " << yuz << ": " << sayac[yuz]
<< " (" << yuzde << "%)" << std::endl;
}
return 0;
}Bu programı çalıştırınca her yüzün yaklaşık %16.67 civarında çıktığını göreceksin. Aynısını rand() % 6 + 1 ile yapsan, küçük ama ölçülebilir bir sapma olurdu.
Pratik Örnek 2: Rastgele Şifre Üretici
Gerçek hayatta işine yarayacak bir şifre üretici yazalım:
#include <random>
#include <iostream>
#include <string>
#include <algorithm>
std::string generatePassword(int length, bool useSpecial = true) {
const std::string lowercase = "abcdefghijklmnopqrstuvwxyz";
const std::string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const std::string digits = "0123456789";
const std::string special = "!@#$%^&*()-_=+[]{}|;:,.<>?";
std::string charset = lowercase + uppercase + digits;
if (useSpecial) charset += special;
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<size_t> dist(0, charset.size() - 1);
std::string password;
password.reserve(length);
for (int i = 0; i < length; i++) {
password += charset[dist(engine)];
}
return password;
}
int main() {
// 16 karakterlik 5 şifre üret
for (int i = 0; i < 5; i++) {
std::cout << generatePassword(16) << std::endl;
}
// Özel karakter olmadan 12 karakterlik
std::cout << "\nÖzel karaktersiz: " << generatePassword(12, false) << std::endl;
return 0;
}⚠️ Dikkat: Bu şifre üretici günlük kullanım için gayet yeterlidir, ama kriptografik güvenlik gerektiren durumlarda (anahtar üretimi vb.)
std::mt19937yerine işletim sisteminin kriptografik RNG'sini kullanmalısın (Linux'ta/dev/urandom, Windows'taCryptGenRandomveyaBCryptGenRandom). Mersenne Twister kriptografik olarak güvenli değildir.
Pratik Örnek 3: Kart Destesi Karıştırma
Bir iskambil destesini karıştırıp dağıtalım. Burada std::shuffle devreye giriyor:
#include <random>
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
struct Card {
std::string suit; // Kupa, Karo, Sinek, Maça
std::string rank; // A, 2, 3, ..., K
std::string toString() const {
return rank + " " + suit;
}
};
int main() {
// Desteyi oluştur
std::vector<std::string> suits = {"♥", "♦", "♣", "♠"};
std::vector<std::string> ranks = {
"A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"
};
std::vector<Card> deck;
for (const auto& suit : suits) {
for (const auto& rank : ranks) {
deck.push_back({suit, rank});
}
}
// Karıştır
std::random_device rd;
std::mt19937 engine(rd());
std::shuffle(deck.begin(), deck.end(), engine);
// İlk 5 kartı göster (poker eli)
std::cout << "Elin: ";
for (int i = 0; i < 5; i++) {
std::cout << deck[i].toString();
if (i < 4) std::cout << ", ";
}
std::cout << std::endl;
return 0;
}std::shuffle ile Container Karıştırma
std::shuffle aslında Fisher-Yates karıştırma algoritmasını uygular. Bu algoritma O(n) zamanda, tamamen uniform (eşit olasılıklı) bir karıştırma yapar.
#include <random>
#include <algorithm>
#include <vector>
#include <iostream>
#include <string>
#include <numeric>
int main() {
// Sayı vektörü karıştırma
std::vector<int> numbers(20);
std::iota(numbers.begin(), numbers.end(), 1); // 1, 2, 3, ..., 20
std::random_device rd;
std::mt19937 engine(rd());
std::shuffle(numbers.begin(), numbers.end(), engine);
std::cout << "Karışık sayılar: ";
for (int n : numbers) std::cout << n << " ";
std::cout << std::endl;
// String vektörü karıştırma
std::vector<std::string> isimler = {
"Ali", "Ayşe", "Mehmet", "Fatma", "Can", "Zeynep"
};
std::shuffle(isimler.begin(), isimler.end(), engine);
std::cout << "Sunum sırası: ";
for (const auto& isim : isimler) std::cout << isim << " ";
std::cout << std::endl;
return 0;
}Eski C++'taki std::random_shuffle fonksiyonu C++14'te deprecated oldu ve C++17'de tamamen kaldırıldı. Sebebi: dahili olarak rand() kullanıyordu. Her zaman std::shuffle kullan.
Neden Kendi Karıştırmanı Yazmamalısın
Sık görülen bir hata şudur:
// YANLIŞ — eşit dağılımlı karıştırma DEĞİL!
for (int i = 0; i < n; i++) {
int j = rand() % n;
std::swap(arr[i], arr[j]);
}Bu "naif" karıştırma n^n olası sonuç üretir, ama n! permütasyon vardır. n^n genellikle n!'e bölünemez, bu da bazı permütasyonların diğerlerinden daha olası olduğu anlamına gelir. Fisher-Yates (ve std::shuffle) bu sorunu doğru çözer:
// DOĞRU — Fisher-Yates algoritması
// (std::shuffle zaten bunu yapar, elle yazmanı gerektirmez)
for (int i = n - 1; i > 0; i--) {
std::uniform_int_distribution<int> dist(0, i);
int j = dist(engine);
std::swap(arr[i], arr[j]);
}Rastgele Sayı Üretiminde İyi Pratikler
Engine'i Bir Kez Oluştur, Çok Kez Kullan
Her sayı üretiminde yeni engine oluşturmak hem yavaş hem de kalitesiz sonuç verir:
// YANLIŞ — her çağrıda yeni engine
int badRandom(int min, int max) {
std::random_device rd;
std::mt19937 engine(rd()); // Her seferinde yeni!
std::uniform_int_distribution<int> dist(min, max);
return dist(engine);
}
// DOĞRU — static engine, bir kez oluştur
int goodRandom(int min, int max) {
static std::mt19937 engine(std::random_device{}());
std::uniform_int_distribution<int> dist(min, max);
return dist(engine);
}static anahtar kelimesi engine'in fonksiyon ilk çağrıldığında bir kez oluşturulmasını ve sonraki çağrılarda aynı engine'in kullanılmasını sağlar. Böylece engine state'i ilerler ve kaliteli sayılar üretmeye devam eder.
Dağılım Nesnelerini Yeniden Kullanabilirsin
Dağılım nesneleri hafiftir (lightweight) — genellikle sadece parametreleri tutarlar. Yeniden kullanmak performans açısından önemli olmasa da, kod temizliği açısından iyi bir pratiktir:
std::mt19937 engine(std::random_device{}());
// Dağılımı bir kez tanımla
std::uniform_int_distribution<int> d6(1, 6);
std::uniform_int_distribution<int> d20(1, 20);
std::normal_distribution<double> hasar(50.0, 10.0);
// Tekrar tekrar kullan
int zar1 = d6(engine);
int zar2 = d6(engine);
int yetenek = d20(engine);
double vuruş = hasar(engine);Thread-Safety Notları
Modern yazılımda çoklu iş parçacığı (multi-threading) çok yaygın. Rastgele sayı üretiminin thread-safety durumu önemli.
Temel Kural: Her Thread'e Kendi Engine'i
std::mt19937 thread-safe değildir. İki thread aynı engine nesnesine aynı anda erişirse tanımsız davranış (undefined behavior) oluşur.
#include <random>
#include <thread>
#include <iostream>
#include <mutex>
// Yaklaşım 1: Thread-local engine (ÖNERİLEN)
int threadSafeRandom(int min, int max) {
// Her thread kendi engine'ine sahip
thread_local std::mt19937 engine(std::random_device{}());
std::uniform_int_distribution<int> dist(min, max);
return dist(engine);
}
// Yaklaşım 2: Mutex ile koruma (daha yavaş)
class SharedRNG {
std::mt19937 engine_;
std::mutex mutex_;
public:
SharedRNG() : engine_(std::random_device{}()) {}
int generate(int min, int max) {
std::lock_guard<std::mutex> lock(mutex_);
std::uniform_int_distribution<int> dist(min, max);
return dist(engine_);
}
};
int main() {
// Thread-local yaklaşımı test et
auto worker = [](int id) {
for (int i = 0; i < 5; i++) {
std::cout << "Thread " << id << ": "
<< threadSafeRandom(1, 100) << std::endl;
}
};
std::thread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}thread_local yaklaşımı genellikle daha iyidir çünkü lock beklemesi (contention) olmaz. Her thread bağımsız çalışır.
random_device Thread-Safety'si
std::random_device implementasyona bağlı olarak thread-safe olabilir veya olmayabilir. Güvenli tarafta kalmak istersen, onu da seed alma aşamasında dikkatli kullan:
// Güvenli yaklaşım: Her thread başlatılırken kendi seed'ini al
void workerThread(unsigned int seed) {
std::mt19937 engine(seed);
std::uniform_int_distribution<int> dist(1, 100);
for (int i = 0; i < 1000; i++) {
int value = dist(engine);
// ... value ile bir şeyler yap
}
}
int main() {
std::random_device rd;
// Ana thread'de seed'leri üret, sonra dağıt
std::thread t1(workerThread, rd());
std::thread t2(workerThread, rd());
std::thread t3(workerThread, rd());
t1.join();
t2.join();
t3.join();
return 0;
}Tekrarlanabilir Sonuçlar (Reproducibility)
Oyun geliştirme, bilimsel simülasyon ve test senaryolarında aynı "rastgele" diziyi tekrar üretebilmek çok değerlidir. Sabit seed kullanarak bunu başarabilirsin:
#include <random>
#include <iostream>
#include <vector>
#include <algorithm>
class ReproducibleSimulation {
std::mt19937 engine_;
public:
// Sabit seed ile tekrarlanabilir
explicit ReproducibleSimulation(unsigned int seed) : engine_(seed) {}
std::vector<int> generateSequence(int count, int min, int max) {
// Engine'i sıfırla — aynı seed, aynı dizi
std::uniform_int_distribution<int> dist(min, max);
std::vector<int> result;
result.reserve(count);
for (int i = 0; i < count; i++) {
result.push_back(dist(engine_));
}
return result;
}
void reset(unsigned int seed) {
engine_.seed(seed);
}
};
int main() {
ReproducibleSimulation sim(12345);
auto seq1 = sim.generateSequence(10, 1, 100);
sim.reset(12345); // Aynı seed ile sıfırla
auto seq2 = sim.generateSequence(10, 1, 100);
// seq1 ve seq2 birebir aynı!
std::cout << "Aynı mı? " << (seq1 == seq2 ? "Evet!" : "Hayır") << std::endl;
return 0;
}Bu yetenek özellikle şu durumlarda çok işe yarar:
Bug tekrarlama: Hatalı davranışa yol açan seed'i kaydedip aynı senaryoyu debug edebilirsin.
Oyun replay'i: Oyuncunun girdileri + seed kaydedilirse, tüm oyun tekrar oynatılabilir.
Birim testi: Rastgele sayılara bağlı kodları deterministic test edebilirsin.
Yaygın Hatalar ve Kaçınılması Gerekenler
1. Her Çağrıda Yeni Seed
// YANLIŞ — ms hassasiyetinde aynı zamana denk gelebilir
for (int i = 0; i < 100; i++) {
std::mt19937 engine(std::chrono::system_clock::now()
.time_since_epoch().count());
std::uniform_int_distribution<int> dist(1, 100);
std::cout << dist(engine) << " "; // Çoğu aynı sayı olabilir!
}2. rand() ile random Kütüphanesini Karıştırmak
// YANLIŞ — iki farklı sistemi karıştırma
srand(time(0));
std::mt19937 engine(rand()); // rand() kalitesiz seed verir3. Dağılım Aralığını Yanlış Anlamak
std::uniform_int_distribution<int> dist(0, 10);
// [0, 10] → 0 dahil, 10 DAHİL (11 farklı değer)
std::uniform_real_distribution<double> dist2(0.0, 1.0);
// [0.0, 1.0) → 0.0 dahil, 1.0 HARİÇTam sayı dağılımında her iki uç dahil, gerçel sayı dağılımında üst sınır hariç. Bu farkı unutma.
Bonus: Ağırlıklı Rastgele Seçim
Oyun geliştirmede sıkça karşılaşılan bir senaryo: farklı olasılıklara sahip öğeler arasından seçim yapmak. Mesela bir düşman %60 bronz, %30 gümüş, %10 altın eşya düşürsün:
#include <random>
#include <iostream>
#include <string>
int main() {
std::random_device rd;
std::mt19937 engine(rd());
// Ağırlıklar: bronz=60, gümüş=30, altın=10
std::discrete_distribution<int> loot_dist({60, 30, 10});
std::string items[] = {"Bronz Kılıç", "Gümüş Kalkan", "Altın Taç"};
// 20 düşman öldür, loot'a bak
for (int i = 0; i < 20; i++) {
int drop = loot_dist(engine); // 0, 1 veya 2
std::cout << "Düşman " << (i+1) << ": " << items[drop] << std::endl;
}
return 0;
}std::discrete_distribution ağırlıkları otomatik normalize eder. Yani {60, 30, 10} vermek ile {6, 3, 1} vermek aynı sonucu verir.
Performans Karşılaştırması
Farklı engine'lerin ve yöntemlerin performansı merak edilebilir. Genel sıralama (hızlıdan yavaşa):
| Yöntem | Yaklaşık Hız | Kalite | Kullanım |
|---|---|---|---|
std::minstd_rand | ★★★★★ | ★★☆☆☆ | Kalite önemli değilse |
std::mt19937 | ★★★★☆ | ★★★★☆ | Genel amaçlı (önerilen) |
std::mt19937_64 | ★★★★☆ | ★★★★☆ | 64-bit ihtiyacı varsa |
std::ranlux48 | ★★☆☆☆ | ★★★★★ | Yüksek kalite gerekirse |
std::random_device | ★☆☆☆☆ | ★★★★★ | Sadece seed için |
💡 İpucu: std::random_device her çağrıda işletim sistemine gider, bu yüzden yavaştır. Onu yalnızca seed almak için kullan, her sayı üretiminde çağırma. Sürekli ihtiyacın varsa engine'e seed ver ve engine'den üret.
Özet
`rand()` ve `srand()` kullanma. Düşük kalite, global state, thread-unsafe ve modulo bias sorunları var. Modern C++
<random>kütüphanesini tercih et.Engine + Distribution modeli rastgele sayı üretimini iki bağımsız parçaya ayırır. Engine ham bitler üretir, dağılım onları istediğin şekle sokar. LEGO gibi birleştir.
`std::mt19937` neredeyse her durumda doğru engine seçimidir. Yüksek kalite, geniş periyot, iyi performans sunar.
Seed için `std::random_device` kullan, ama bazı platformlarda deterministik olabileceğini unutma. Ciddi işlerde
seed_seqile birden fazla değerden seed oluştur.`std::shuffle` container'ları karıştırmak için doğru yoldur. Eski
random_shuffledeprecated — kesinlikle kullanma.Thread-safety için `thread_local` engine kullan. Her thread'in kendi engine'i olsun, mutex'e gerek kalmasın.
Tekrarlanabilirlik için sabit seed kullan. Debug, test ve replay senaryolarında hayat kurtarır.
AI Asistan
Sorularını yanıtlamaya hazır