Mengapa rand () mengulang angka jauh lebih sering di Linux daripada Mac?

88

Saya menerapkan hashmap dalam C sebagai bagian dari proyek yang sedang saya kerjakan dan menggunakan sisipan acak untuk mengujinya ketika saya perhatikan bahwa rand()di Linux tampaknya mengulangi angka jauh lebih sering daripada di Mac. RAND_MAXadalah 2147483647 / 0x7FFFFFFF di kedua platform. Saya telah menguranginya menjadi program pengujian ini yang membuat array byte- RAND_MAX+1panjang, menghasilkan RAND_MAXangka acak, mencatat jika masing-masing adalah duplikat, dan memeriksanya dari daftar seperti yang terlihat.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main() {
    size_t size = ((size_t)RAND_MAX) + 1;
    char *randoms = calloc(size, sizeof(char));
    int dups = 0;
    srand(time(0));
    for (int i = 0; i < RAND_MAX; i++) {
        int r = rand();
        if (randoms[r]) {
            // printf("duplicate at %d\n", r);
            dups++;
        }
        randoms[r] = 1;
    }
    printf("duplicates: %d\n", dups);
}

Linux secara konsisten menghasilkan sekitar 790 juta duplikat. Mac secara konsisten hanya menghasilkan satu, sehingga loop melalui setiap nomor acak yang dapat dihasilkannya hampir tanpa berulang. Adakah yang bisa menjelaskan kepada saya bagaimana ini bekerja? Saya tidak dapat mengatakan hal yang berbeda dari halaman manual, tidak bisa membedakan RNG mana yang digunakan, dan tidak dapat menemukan apa pun secara online. Terima kasih!

Theron S
sumber
4
Karena rand () mengembalikan nilai dari 0..RAND_MAX inklusif, array Anda harus berukuran RAND_MAX + 1
Blastfurnace
21
Anda mungkin telah memperhatikan bahwa RAND_MAX / e ~ = 790 juta. Juga batas (1-1 / n) ^ n saat n mendekati tak terhingga adalah 1 / e.
David Schwartz
3
@ DavidSchwartz Jika saya mengerti Anda dengan benar, itu mungkin menjelaskan mengapa angka di Linux secara konsisten sekitar 790 juta. Saya kira pertanyaannya kemudian adalah: mengapa / bagaimana Mac tidak mengulangi itu berkali-kali?
Theron S
26
Tidak ada persyaratan kualitas untuk PRNG di pustaka runtime. Hanya persyaratan nyata adalah pengulangan dengan benih yang sama. Rupanya, kualitas PRNG di linux Anda lebih baik daripada di Mac Anda.
Pukul
4
@ chux Ya, tetapi karena ini didasarkan pada perkalian, negara tidak pernah bisa nol atau hasilnya (negara bagian berikutnya) juga akan menjadi nol. Berdasarkan kode sumber, ia memeriksa nol sebagai kasus khusus jika diunggulkan dengan nol, tetapi tidak pernah menghasilkan nol sebagai bagian dari urutan.
Arkku

Jawaban:

119

Meskipun pada awalnya mungkin terdengar seperti macOS rand()entah bagaimana lebih baik untuk tidak mengulangi angka, orang harus mencatat bahwa dengan jumlah angka yang dihasilkan ini diharapkan akan melihat banyak duplikat (pada kenyataannya, sekitar 790 juta, atau (2 31 -1) ) / e ). Demikian juga iterasi melalui angka dalam urutan juga tidak akan menghasilkan duplikat, tetapi tidak akan dianggap sangat acak. Jadi rand()implementasi Linux dalam tes ini tidak dapat dibedakan dari sumber acak yang benar, sedangkan macOS rand()tidak.

Hal lain yang tampak mengejutkan pada pandangan pertama adalah bagaimana macOS rand()dapat mengatur untuk menghindari duplikat dengan baik. Melihat kode sumbernya , kami menemukan implementasinya sebagai berikut:

/*
 * Compute x = (7^5 * x) mod (2^31 - 1)
 * without overflowing 31 bits:
 *      (2^31 - 1) = 127773 * (7^5) + 2836
 * From "Random number generators: good ones are hard to find",
 * Park and Miller, Communications of the ACM, vol. 31, no. 10,
 * October 1988, p. 1195.
 */
    long hi, lo, x;

    /* Can't be initialized with 0, so use another value. */
    if (*ctx == 0)
        *ctx = 123459876;
    hi = *ctx / 127773;
    lo = *ctx % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    return ((*ctx = x) % ((unsigned long) RAND_MAX + 1));

Ini memang menghasilkan semua angka antara 1 dan RAND_MAX, termasuk, tepat sekali, sebelum urutan berulang. Karena negara berikutnya didasarkan pada perkalian, negara tidak pernah bisa nol (atau semua negara masa depan juga akan menjadi nol). Jadi angka berulang yang Anda lihat adalah yang pertama, dan nol adalah yang tidak pernah dikembalikan.

Apple telah mempromosikan penggunaan generator angka acak yang lebih baik dalam dokumentasi dan contoh-contoh mereka selama setidaknya selama macOS (atau OS X) ada, sehingga kualitasnya rand()mungkin tidak dianggap penting, dan mereka baru saja terjebak dengan salah satu dari mereka. generator pseudorandom paling sederhana yang tersedia. (Seperti yang Anda catat, mereka rand()bahkan berkomentar dengan rekomendasi untuk digunakan arc4random()sebagai gantinya.)

Pada catatan terkait, generator nomor pseudorandom paling sederhana yang dapat saya temukan yang menghasilkan hasil yang layak dalam tes acak (dan banyak lainnya) untuk keacakan adalah xorshift * :

uint64_t x = *ctx;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*ctx = x;
return (x * 0x2545F4914F6CDD1DUL) >> 33;

Implementasi ini menghasilkan hampir persis 790 juta duplikat dalam pengujian Anda.

Arkku
sumber
5
Sebuah artikel jurnal yang diterbitkan pada 1980-an mengusulkan uji statistik untuk PRNG berdasarkan "masalah ulang tahun".
pjs
14
"Apple telah mempromosikan penggunaan generator angka acak yang lebih baik dalam dokumentasi mereka" -> tentu saja Apple dapat menggunakan arc4random()kode suka di belakang rand()dan mendapatkan rand()hasil yang baik . Daripada mencoba mengarahkan programmer ke kode berbeda, buat saja fungsi perpustakaan yang lebih baik. "Mereka baru saja terjebak" adalah pilihan mereka.
chux - Reinstate Monica
23
kurangnya offset konstan pada mac rand()membuatnya sangat buruk sehingga tidak berguna untuk penggunaan praktis: Mengapa rand ()% 7 selalu mengembalikan 0? , Rand ()% 14 hanya menghasilkan nilai 6 atau 13
phuclv
4
@PeterCordes: Ada persyaratan seperti randitu, yang menjalankannya kembali dengan seed yang sama menghasilkan urutan yang sama. OpenBSD randrusak dan tidak mematuhi kontrak ini.
R .. GitHub BERHENTI MEMBANTU ICE
8
@ R..GitHubSTOPHELPINGICE Apakah Anda melihat persyaratan C bahwa rand()dengan seed yang sama menghasilkan urutan yang sama di antara versi perpustakaan yang berbeda? Jaminan semacam itu mungkin berguna untuk pengujian regresi antara versi pustaka, namun saya tidak menemukan persyaratan C untuk itu.
chux
34

MacOS menyediakan fungsi rand () tidak berdokumen di stdlib. Jika Anda membiarkannya tidak diunggulkan, maka nilai pertama yang dihasilkannya adalah 16807, 282475249, 1622650073, 984943658 dan 1144108930. Pencarian cepat akan menunjukkan bahwa urutan ini sesuai dengan generator nomor acak LCG yang sangat dasar yang mengulangi rumus berikut:

x n +1 = 7 5 · x n (mod 2 31 - 1)

Karena keadaan RNG ini dijelaskan sepenuhnya oleh nilai integer 32-bit tunggal, periodenya tidak terlalu lama. Tepatnya, ia mengulangi dirinya sendiri setiap 2 31 - 2 iterasi, menghasilkan setiap nilai dari 1 hingga 2 31 - 2.

Saya tidak berpikir ada implementasi standar rand () untuk semua versi Linux, tetapi ada fungsi glibc rand () yang sering digunakan. Alih-alih variabel negara 32-bit tunggal, ini menggunakan kumpulan lebih dari 1000 bit, yang untuk semua maksud dan tujuan tidak akan pernah menghasilkan urutan berulang sepenuhnya. Sekali lagi, Anda mungkin dapat mengetahui versi apa yang Anda miliki dengan mencetak beberapa keluaran pertama dari RNG ini tanpa menaburinya terlebih dahulu. (Fungsi glibc rand () menghasilkan angka 1804289383, 846930886, 1681692777, 1714636915 dan 1957747793.)

Jadi alasan Anda mendapatkan lebih banyak tabrakan di Linux (dan hampir tidak ada di MacOS) adalah bahwa versi Linux rand () pada dasarnya lebih acak.

r3mainer
sumber
5
yang tidak diunggulkan rand()harus berperilaku seperti orang yangsrand(1);
pmg
5
Kode sumber untuk rand()in macOS tersedia: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/... FWIW, saya menjalankan tes yang sama terhadap ini yang dikompilasi dari sumber dan itu memang menghasilkan hanya satu duplikat. Apple telah mempromosikan penggunaan generator angka acak lainnya (seperti arc4random()sebelum Swift mengambil alih) dalam contoh dan dokumentasi mereka, sehingga penggunaannya rand()mungkin tidak terlalu umum di aplikasi asli pada platform mereka, yang mungkin menjelaskan mengapa itu tidak lebih baik.
Arkku
Terima kasih atas jawabannya, yang menjawab pertanyaan saya. Dan periode (2 ^ 31) -2 menjelaskan mengapa itu akan mulai berulang tepat di akhir seperti yang saya amati. Anda (@ r3mainer) mengatakan rand()tidak berdokumen, tetapi @Arkku telah memberikan tautan ke sumber yang jelas. Apakah Anda berdua tahu mengapa saya tidak dapat menemukan file itu di sistem saya, dan mengapa saya hanya melihat int rand(void) __swift_unavailable("Use arc4random instead.");di Mac stdlib.h? Saya kira kode @Arkku tertaut ke hanya dikompilasi ke ... perpustakaan apa?
Theron S
1
@TheronS Ini dikompilasi ke perpustakaan C, libc /usr/lib/libc.dylib,. =)
Arkku
5
Versi yang rand()diberikan C kegunaan program yang tidak ditentukan oleh "compiler" atau "sistem operasi", melainkan pelaksanaan standar C library (misalnya, glibc, libc.dylib, msvcrt*.dll).
Peter O.
10

rand()didefinisikan oleh standar C, dan standar C tidak menentukan algoritma mana yang digunakan. Jelas, Apple menggunakan algoritma yang lebih rendah untuk implementasi GNU / Linux Anda: Yang Linux tidak dapat dibedakan dari sumber acak sejati dalam pengujian Anda, sedangkan implementasi Apple hanya mengacak angka-angka di sekitar.

Jika Anda menginginkan angka acak dari kualitas apa pun, gunakan PRNG yang lebih baik yang memberikan setidaknya beberapa jaminan pada kualitas nomor yang dikembalikan, atau cukup baca dari /dev/urandomatau serupa. Nanti memberi Anda nomor kualitas kriptografis, tetapi lambat. Bahkan jika terlalu lambat dengan sendirinya, /dev/urandomdapat menyediakan beberapa biji unggul untuk beberapa lainnya, PRNG lebih cepat.

cmaster - mengembalikan monica
sumber
Terima kasih balasannya. Saya sebenarnya tidak membutuhkan PRNG yang baik, hanya khawatir ada beberapa perilaku tidak jelas yang bersembunyi di dalam hashmap saya, lalu merasa penasaran ketika saya menghilangkan kemungkinan itu dan platform masih berperilaku berbeda.
Theron S
btw di sini adalah contoh dari generator nomor acak yang aman secara kriptografis: github.com/divinity76/phpcpp/commit/… - tapi C ++ alih-alih C dan saya membiarkan implementor STL melakukan semua pengangkatan berat ..
hanshenrik
3
@hanshenrik Sebuah crypto RNG umumnya berlebihan & terlalu lambat untuk tabel hash sederhana.
PM 2Ring
1
@ PM2Ring Benar-Benar. Tabel hash hash terutama harus cepat, tidak baik. Namun, jika Anda ingin mengembangkan algoritma tabel hash yang tidak hanya cepat tetapi juga layak, saya percaya itu bermanfaat untuk mengetahui beberapa trik algoritma hash kriptografi. Ini akan membantu Anda menghindari sebagian besar kesalahan paling mencolok yang membingungkan algoritma hash paling cepat. Namun demikian, saya tidak akan mengiklankan implementasi tertentu di sini.
cmaster - mengembalikan monica
@ cmaster Cukup benar. Ini tentu ide yang baik untuk mengetahui sedikit tentang hal-hal seperti fungsi pencampuran dan efek longsoran salju . Untungnya ada fungsi hash non-crypto dengan properti bagus yang tidak mengorbankan terlalu banyak kecepatan (bila diterapkan dengan benar), misalnya xxhash, murmur3, atau siphash.
PM 2Ring
5

Secara umum, pasangan rand / srand telah dianggap semacam usang sejak lama karena bit orde rendah menampilkan lebih sedikit keacakan daripada bit orde tinggi dalam hasil. Ini mungkin atau mungkin tidak ada hubungannya dengan hasil Anda, tetapi saya pikir ini masih merupakan kesempatan yang baik untuk diingat bahwa meskipun beberapa implementasi rand / srand sekarang lebih mutakhir, implementasi yang lebih lama bertahan dan lebih baik menggunakan secara acak (3 ). Di kotak Arch Linux saya, catatan berikut masih ada di halaman manual untuk rand (3):

  The versions of rand() and srand() in the Linux C Library use the  same
   random number generator as random(3) and srandom(3), so the lower-order
   bits should be as random as the higher-order bits.  However,  on  older
   rand()  implementations,  and  on  current implementations on different
   systems, the lower-order bits are much less random than the  higher-or-
   der bits.  Do not use this function in applications intended to be por-
   table when good randomness is needed.  (Use random(3) instead.)

Tepat di bawah itu, halaman manual sebenarnya memberikan contoh implementasi rand dan srand yang sangat pendek, sangat sederhana, tentang RNG LC paling sederhana yang pernah Anda lihat dan memiliki RAND_MAX kecil. Saya tidak berpikir mereka cocok dengan apa yang ada di perpustakaan standar C, jika mereka pernah melakukannya. Atau setidaknya saya harap tidak.

Secara umum, jika Anda akan menggunakan sesuatu dari pustaka standar, gunakan secara acak jika Anda bisa (halaman manual mencantumkannya sebagai standar POSIX kembali ke POSIX.1-2001, tetapi rand adalah cara standar kembali sebelum C bahkan distandarisasi) . Atau lebih baik lagi, pecahkan Numerical Recipes (atau cari secara online) atau Knuth dan implementasikan. Mereka sangat mudah dan Anda hanya perlu melakukannya sekali untuk memiliki tujuan umum RNG dengan atribut yang paling sering Anda butuhkan dan yang memiliki kualitas yang dikenal.

Thomas Kammeyer
sumber
Terima kasih untuk konteksnya. Saya sebenarnya tidak membutuhkan keacakan berkualitas tinggi, dan telah menerapkan MT19937, meskipun di Rust. Sebagian besar hanya ingin tahu tentang bagaimana mencari tahu mengapa kedua platform berperilaku berbeda.
Theron S
1
Kadang-kadang pertanyaan terbaik diajukan karena minat yang sederhana alih-alih kebutuhan yang ketat - sepertinya itu adalah pertanyaan yang sering memberikan jawaban yang baik dari titik keingintahuan tertentu. Anda adalah salah satunya. Ini untuk semua orang yang ingin tahu, peretas asli dan asli.
Thomas Kammeyer
Lucu sekali bahwa sarannya adalah "berhenti menggunakan rand ()" daripada membuat rand () menjadi lebih baik. Tidak ada dalam standar yang pernah mengatakan bahwa itu harus menjadi generator spesifik.
pipa
2
@pipe Jika membuat rand()'lebih baik' berarti membuatnya lebih lambat (yang mungkin akan terjadi - angka acak yang aman secara kriptografis membutuhkan banyak usaha), maka mungkin lebih baik untuk tetap cepat meskipun sedikit lebih mudah diprediksi. Contoh kasus: kami memiliki aplikasi produksi yang membutuhkan waktu lama untuk memulai, yang kami lacak ke RNG yang inisialisasi perlu menunggu entropi yang cukup untuk dihasilkan ... Ternyata itu tidak perlu begitu aman, jadi ganti dengan RNG yang 'lebih buruk' merupakan peningkatan besar.
gidds