<random> menghasilkan nomor yang sama di Linux, tetapi tidak di Windows

90

Kode di bawah ini dimaksudkan untuk menghasilkan daftar lima nomor pseudo-random dalam interval [1.100]. Saya menyemai default_random_enginedengan time(0), yang mengembalikan waktu sistem dalam waktu unix . Ketika saya mengkompilasi dan menjalankan program ini di Windows 7 menggunakan Microsoft Visual Studio 2013, ini berfungsi seperti yang diharapkan (lihat di bawah). Ketika saya melakukannya di Arch Linux dengan kompiler g ++, bagaimanapun, itu berperilaku aneh.

Di Linux, 5 angka akan dihasilkan setiap kali. 4 angka terakhir akan berbeda pada setiap eksekusi (seperti yang sering terjadi), tetapi angka pertama akan tetap sama.

Contoh keluaran dari 5 eksekusi di Windows dan Linux:

      | Windows:       | Linux:        
---------------------------------------
Run 1 | 54,01,91,73,68 | 25,38,40,42,21
Run 2 | 46,24,16,93,82 | 25,78,66,80,81
Run 3 | 86,36,33,63,05 | 25,17,93,17,40
Run 4 | 75,79,66,23,84 | 25,70,95,01,54
Run 5 | 64,36,32,44,85 | 25,09,22,38,13

Menambah misteri, angka pertama itu bertambah satu secara berkala di Linux. Setelah mendapatkan keluaran di atas, saya menunggu sekitar 30 menit dan mencoba lagi untuk menemukan bahwa angka pertama telah berubah dan sekarang selalu dihasilkan sebagai 26. Angka ini terus bertambah 1 secara berkala dan sekarang menjadi 32. Tampaknya sesuai dengan nilai perubahan time(0).

Mengapa angka pertama jarang berubah saat berjalan, dan jika berubah, bertambah 1?

Kode. Ini dengan rapi mencetak 5 angka dan waktu sistem:

#include <iostream>
#include <random>
#include <time.h>

using namespace std;

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    time_t system_time = time(0);    

    default_random_engine e(system_time);
    uniform_int_distribution<int> u(lower_bound, upper_bound);

    cout << '#' << '\t' << "system time" << endl
         << "-------------------" << endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);
        cout << secret << '\t' << system_time << endl;
    }   

    system("pause");
    return 0;
}
Amin Mesbah
sumber
3
Apa itu sizeof(time_t)vs. sizeof(default_random_engine::result_type)?
Mark Ransom
3
Perhatikan bahwa default_random_enginekedua platform tersebut sangat berbeda.
TC
1
Ini masih bisa BTW acak.
Alec Teal
5
Apakah setiap programmer melalui fase di mana mereka berpikir bahwa waktu adalah seed generator nomor acak yang baik?
OldFart
6
@OldFart Ya, itu disebut akademisi.
Casey

Jawaban:

141

Inilah yang terjadi:

  • default_random_enginedi libstdc ++ (pustaka standar GCC) adalah minstd_rand0, yang merupakan mesin kongruensial linier sederhana:

    typedef linear_congruential_engine<uint_fast32_t, 16807, 0, 2147483647> minstd_rand0;
    
  • Cara mesin ini menghasilkan angka acak adalah x i + 1 = (16807x i + 0) mod 2147483647.

  • Oleh karena itu, jika benih berbeda dengan 1, maka sebagian besar waktu jumlah pertama yang dihasilkan akan berbeda 16807.

  • Kisaran generator ini adalah [1, 2147483646]. Cara libstdc ++ uniform_int_distributionmemetakannya menjadi integer dalam kisaran [1, 100] pada dasarnya adalah ini: menghasilkan angka n. Jika angkanya tidak lebih besar dari 2147483600, maka kembalikan (n - 1) / 21474836 + 1; jika tidak, coba lagi dengan nomor baru.

    Seharusnya mudah untuk melihat bahwa dalam sebagian besar kasus, dua ns yang hanya berbeda 16807 akan menghasilkan angka yang sama dalam [1, 100] di bawah prosedur ini. Faktanya, orang akan mengharapkan jumlah yang dihasilkan meningkat satu setiap 21474836/16807 = 1278 detik atau 21,3 menit, yang sesuai dengan pengamatan Anda.

MSVC ini default_random_engineadalah mt19937, yang tidak memiliki masalah ini.

TC
sumber
36
Saya bertanya-tanya apa yang dimiliki para pengembang perpustakaan standar GCC untuk memilih default yang mengerikan.
CodesInChaos
13
@CodesInChaos Saya tidak tahu apakah itu terkait dengan tidak tetapi rantai alat MacOS / iOS juga menggunakan mesin acak mengerikan yang sama, membuat rand()% 7 selalu mengembalikan 0
phuclv
7
@ LưuVĩnhPhúc Tidak memperbaiki rand()agak bisa dimengerti (itu omong kosong warisan tanpa harapan). Menggunakan PRNG shit-tier untuk sesuatu yang baru tidak bisa dimaafkan. Saya bahkan akan menganggap ini sebagai pelanggaran standar, karena standar tersebut mensyaratkan "sediakan setidaknya perilaku mesin yang dapat diterima untuk penggunaan yang relatif santai, tidak ahli, dan / atau ringan." yang tidak disediakan oleh implementasi ini karena gagal total bahkan untuk kasus penggunaan sepele seperti rand % 7contoh Anda .
CodesInChaos
2
@CodesInChaos Mengapa tidak memperbaiki rand()agak bisa dimengerti? Apakah hanya karena tidak ada yang berpikir untuk melakukannya?
pengguna253751
2
@immibis API sangat rusak sehingga Anda lebih baik dengan penggantian independen yang memperbaiki semua masalah. 1) Mengganti algoritme akan menjadi perubahan yang mengganggu, jadi Anda mungkin memerlukan sakelar kompatibilitas untuk program yang lebih lama. 2) Benih srandterlalu kecil untuk menghasilkan benih yang unik dengan mudah. 3) Ini mengembalikan integer dengan implementasi yang ditentukan batas atas yang pemanggil entah bagaimana harus mengurangi ke angka dalam kisaran yang diinginkan, yang bila dilakukan dengan benar lebih banyak pekerjaan daripada menulis pengganti dengan API yang waras untuk rand()4) Menggunakan status global yang bisa berubah
CodesInChaos
30

The std::default_random_engineadalah implementasi didefinisikan. Gunakan std::mt19937atau std::mt19937_64sebagai gantinya.

Selain itu, std::timedan ctimefungsinya tidak terlalu akurat, gunakan jenis yang ditentukan di <chrono>header sebagai gantinya:

#include <iostream>
#include <random>
#include <chrono>

int main()
{
    const int upper_bound = 100;
    const int lower_bound = 1;

    auto t = std::chrono::high_resolution_clock::now().time_since_epoch().count();

    std::mt19937 e;
    e.seed(static_cast<unsigned int>(t)); //Seed engine with timed value.
    std::uniform_int_distribution<int> u(lower_bound, upper_bound);

    std::cout << '#' << '\t' << "system time" << std::endl
    << "-------------------" << std::endl;

    for (int counter = 1; counter <= 5; counter++)
    {
        int secret = u(e);

        std::cout << secret << '\t' << t << std::endl;
    }   

    system("pause");
    return 0;
}
Casey
sumber
3
Apakah diinginkan untuk menggunakan waktu yang lebih akurat saat melakukan seeding pada generator variabel pseudo-random? Mungkin ini naif, tetapi sepertinya ketidakakuratan hampir diinginkan jika memperkenalkan entropi. (Kecuali yang Anda maksud itu kurang tepat dan dengan demikian menghasilkan benih potensial yang lebih sedikit secara material.)
Nat
15
Saya hanya akan menyarankan menggunakan std::random_devicealih-alih current_time untuk menyemai generator acak Anda. Silakan periksa contoh cppreference tentang Random.
Aleksander Fular
5
Jika Anda tidak ingin siapa pun menebak benih Anda (dan karena itu mereproduksi urutan Anda) kurang presisi tidak sama dengan lebih banyak keacakan. Mari kita pergi ke ekstrem: Bulatkan benih Anda ke hari berikutnya (atau tahun?) -> menebak itu mudah. Gunakan presisi femtosecond -> Banyak tebakan yang harus dilakukan ...
linac
2
@ChemicalEngineer Granularitasnya ctimeadalah 1 detik. Granularitas std::chronoimplementasi ditentukan oleh pengguna, default ke, untuk std::high_resolution_clock(dalam Visual Studio ini adalah typedef untuk std::steady_clock), nanodetik tetapi dapat memilih pengukuran yang jauh lebih kecil, oleh karena itu, jauh lebih tepat.
Casey
2
@linac Jika Anda menginginkan properti kriptografi, Anda akan menggunakan prng yang sesuai (bukan yang digunakan dalam jawaban ini). Dan tentu saja benih berbasis waktu juga keluar dari pertanyaan, tidak peduli presisi yang dijanjikan.
Cthulhu
-2

Di Linux, fungsi acak bukanlah fungsi acak dalam arti probabilistik, tetapi generator bilangan acak semu. Itu diasinkan dengan biji, dan berdasarkan biji itu, jumlah yang dihasilkan acak semu dan tersebar merata. Cara Linux memiliki keuntungan bahwa dalam desain eksperimen tertentu yang menggunakan informasi dari populasi, dapat diukur pengulangan eksperimen dengan tweaker informasi input yang diketahui. Ketika program terakhir siap untuk pengujian kehidupan nyata, garam (benih), dapat dibuat dengan meminta pengguna untuk menggerakkan mouse, mencampur gerakan mouse dengan beberapa penekanan tombol dan menambahkan sejumput hitungan mikrodetik sejak awal kekuatan terakhir.

Benih nomor acak Windows diperoleh dari kumpulan nomor mouse, keyboard, jaringan dan waktu hari. Itu tidak dapat diulang. Tetapi nilai garam ini dapat disetel ulang ke benih yang diketahui, jika seperti yang disebutkan di atas, seseorang terlibat dalam desain percobaan.

Oh ya, Linux memiliki dua generator bilangan acak. Satu, defaultnya adalah modulo 32bits, dan yang lainnya adalah modulo 64bits. Pilihan Anda bergantung pada kebutuhan akurasi dan jumlah waktu komputasi yang ingin Anda gunakan untuk pengujian atau penggunaan sebenarnya.

Leslie Satenstein
sumber
5
Saya tidak yakin mengapa Anda berbicara tentang algoritma generasi benih. OP jelas menggunakan waktu sistem sebagai benih. Selain itu, dapatkah Anda menambahkan beberapa referensi kecollection of mouse, keyboard, network and time of day numbers
lokal default