← Kursa Dön
📄 Text · 20 min

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_device bazı platformlarda (özellikle MinGW gibi bazı Windows derleyicilerinde) deterministik olabilir — yani her çalıştırmada aynı değeri döndürür! Bu durumda random_device aslında rastgele değildir. Kodunu cross-platform yapıyorsan, bunu test et. Alternatif olarak random_device ile 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::mt19937 yerine işletim sisteminin kriptografik RNG'sini kullanmalısın (Linux'ta /dev/urandom, Windows'ta CryptGenRandom veya BCryptGenRandom). 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 verir

3. 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öntemYaklaşık HızKaliteKullanı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_seq ile birden fazla değerden seed oluştur.

  • `std::shuffle` container'ları karıştırmak için doğru yoldur. Eski random_shuffle deprecated — 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.