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_MAX
adalah 2147483647 / 0x7FFFFFFF di kedua platform. Saya telah menguranginya menjadi program pengujian ini yang membuat array byte- RAND_MAX+1
panjang, menghasilkan RAND_MAX
angka 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!
Jawaban:
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. Jadirand()
implementasi Linux dalam tes ini tidak dapat dibedakan dari sumber acak yang benar, sedangkan macOSrand()
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: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, merekarand()
bahkan berkomentar dengan rekomendasi untuk digunakanarc4random()
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 * :
Implementasi ini menghasilkan hampir persis 790 juta duplikat dalam pengujian Anda.
sumber
arc4random()
kode suka di belakangrand()
dan mendapatkanrand()
hasil yang baik . Daripada mencoba mengarahkan programmer ke kode berbeda, buat saja fungsi perpustakaan yang lebih baik. "Mereka baru saja terjebak" adalah pilihan mereka.rand()
membuatnya sangat buruk sehingga tidak berguna untuk penggunaan praktis: Mengapa rand ()% 7 selalu mengembalikan 0? , Rand ()% 14 hanya menghasilkan nilai 6 atau 13rand
itu, yang menjalankannya kembali dengan seed yang sama menghasilkan urutan yang sama. OpenBSDrand
rusak dan tidak mematuhi kontrak ini.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.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:
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.
sumber
rand()
harus berperilaku seperti orang yangsrand(1);
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 (sepertiarc4random()
sebelum Swift mengambil alih) dalam contoh dan dokumentasi mereka, sehingga penggunaannyarand()
mungkin tidak terlalu umum di aplikasi asli pada platform mereka, yang mungkin menjelaskan mengapa itu tidak lebih baik.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 melihatint rand(void) __swift_unavailable("Use arc4random instead.");
di Macstdlib.h
? Saya kira kode @Arkku tertaut ke hanya dikompilasi ke ... perpustakaan apa?/usr/lib/libc.dylib
,. =)rand()
diberikan C kegunaan program yang tidak ditentukan oleh "compiler" atau "sistem operasi", melainkan pelaksanaan standar C library (misalnya,glibc
,libc.dylib
,msvcrt*.dll
).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/urandom
atau serupa. Nanti memberi Anda nomor kualitas kriptografis, tetapi lambat. Bahkan jika terlalu lambat dengan sendirinya,/dev/urandom
dapat menyediakan beberapa biji unggul untuk beberapa lainnya, PRNG lebih cepat.sumber
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):
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.
sumber
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.