Saya mengamati bahwa rand()
fungsi perpustakaan ketika dipanggil hanya sekali dalam satu lingkaran, hampir selalu menghasilkan angka positif.
for (i = 0; i < 100; i++) {
printf("%d\n", rand());
}
Tetapi ketika saya menambahkan dua rand()
panggilan, nomor yang dihasilkan sekarang memiliki lebih banyak angka negatif.
for (i = 0; i < 100; i++) {
printf("%d = %d\n", rand(), (rand() + rand()));
}
Dapatkah seseorang menjelaskan mengapa saya melihat angka negatif dalam kasus kedua?
PS: Saya menginisialisasi benih sebelum loop sebagai srand(time(NULL))
.
rand()
tidak boleh negatif ...RAND_MAX
untuk kompiler Anda? Anda biasanya dapat menemukannya distdlib.h
. (Lucu: memeriksaman 3 rand
, ini berisi deskripsi satu baris "generator nomor acak buruk".)abs(rand()+rand())
. Saya lebih suka memiliki UB positif daripada yang negatif! ;)Jawaban:
rand()
didefinisikan untuk mengembalikan integer antara0
danRAND_MAX
.bisa meluap. Apa yang Anda amati kemungkinan merupakan hasil dari perilaku tidak terdefinisi yang disebabkan oleh integer overflow.
sumber
a + b > a
jika mereka tahu itub > 0
. Mereka juga dapat mengasumsikan bahwa jika ada pernyataan yang dieksekusia + 5
kemudian maka nilai saat ini lebih rendah dari ituINT_MAX - 5
. Jadi, bahkan pada pelengkap 2 prosesor / juru bahasa tanpa program perangkap mungkin tidak berperilaku seolah-olahint
itu adalah pelengkap 2 tanpa perangkap.Masalahnya adalah penambahan.
rand()
mengembalikanint
nilai0...RAND_MAX
. Jadi, jika Anda menambahkan dua dari mereka, Anda akan bangunRAND_MAX * 2
. Jika itu melebihiINT_MAX
, hasil penambahan melebihi rentang yang valid yangint
dapat ditampung. Melimpahnya nilai yang ditandatangani adalah perilaku yang tidak terdefinisi dan dapat menyebabkan papan ketik Anda berbicara kepada Anda dalam bahasa asing.Karena tidak ada keuntungan di sini dalam menambahkan dua hasil acak, ide sederhana adalah tidak melakukannya. Atau Anda dapat memberikan setiap hasil
unsigned int
sebelum penambahan jika itu dapat menahan jumlah. Atau gunakan tipe yang lebih besar. Catatan yanglong
belum tentu lebih luas dariint
, hal yang sama berlakulong long
jikaint
setidaknya 64 bit!Kesimpulan: Hindari saja penambahan. Itu tidak memberikan lebih banyak "keacakan". Jika Anda membutuhkan lebih banyak bit, Anda bisa menggabungkan nilainya
sum = a + b * (RAND_MAX + 1)
, tetapi itu juga kemungkinan membutuhkan tipe data yang lebih besar daripadaint
.Karena alasan yang Anda nyatakan adalah untuk menghindari hasil nol: Itu tidak dapat dihindari dengan menambahkan hasil dari dua
rand()
panggilan, karena keduanya bisa nol. Sebagai gantinya, Anda hanya bisa menambah. JikaRAND_MAX == INT_MAX
, ini tidak dapat dilakukan diint
. Namun,(unsigned int)rand() + 1
akan melakukan sangat, sangat mungkin. Kemungkinan (tidak pasti), karena memang membutuhkanUINT_MAX > INT_MAX
, yang benar pada semua implementasi yang saya ketahui (yang mencakup beberapa arsitektur tertanam, DSP, dan semua platform desktop, seluler, dan server selama 30 tahun terakhir).Peringatan:
Meskipun sudah ditaburkan di komentar di sini, harap dicatat bahwa menambahkan dua nilai acak tidak mendapatkan distribusi yang seragam, tetapi distribusi segitiga seperti menggulung dua dadu: untuk mendapatkan
12
(dua dadu) kedua dadu harus ditampilkan6
. karena11
sudah ada dua varian yang mungkin:6 + 5
atau5 + 6
, dll.Jadi, penambahannya juga buruk dari aspek ini.
Juga catat bahwa hasil yang
rand()
dihasilkan tidak independen satu sama lain, karena dihasilkan oleh generator nomor pseudorandom . Perhatikan juga bahwa standar tidak menentukan kualitas atau distribusi seragam dari nilai yang dihitung.sumber
UINT_MAX > INT_MAX != false
dijamin oleh standar. (Mungkin terdengar, tetapi tidak yakin jika diperlukan). Jika demikian, Anda hanya dapat memberikan satu hasil dan penambahan (dalam urutan itu!).Ini adalah jawaban untuk klarifikasi dari pertanyaan yang dibuat dalam komentar untuk jawaban ini ,
Masalahnya adalah untuk menghindari 0. Ada (setidaknya) dua masalah dengan solusi yang diusulkan. Salah satunya adalah, seperti yang ditunjukkan oleh jawaban lainnya, yang
rand()+rand()
dapat memicu perilaku tidak terdefinisi. Saran terbaik adalah jangan pernah memaksakan perilaku yang tidak terdefinisi. Masalah lainnya adalah tidak ada jaminan yangrand()
tidak akan menghasilkan 0 dua kali berturut-turut.Berikut ini menolak nol, menghindari perilaku tidak terdefinisi, dan dalam sebagian besar kasus akan lebih cepat dari dua panggilan ke
rand()
:sumber
rand() + 1
?Pada dasarnya
rand()
menghasilkan angka antara0
danRAND_MAX
, dan2 RAND_MAX > INT_MAX
dalam kasus Anda.Anda dapat modulus dengan nilai maksimal tipe data Anda untuk mencegah overflow. Kursus ini akan mengganggu distribusi angka acak, tetapi
rand
hanya cara untuk mendapatkan angka acak cepat.sumber
Mungkin Anda bisa mencoba pendekatan yang agak rumit dengan memastikan bahwa nilai yang dikembalikan dengan jumlah 2 rand () tidak pernah melebihi nilai RAND_MAX. Suatu pendekatan yang mungkin bisa berupa penjumlahan = rand () / 2 + rand () / 2; Ini akan memastikan bahwa untuk kompiler 16 bit dengan nilai RAND_MAX dari 32767 bahkan jika kedua rand terjadi untuk mengembalikan 32767, bahkan kemudian (32767/2 = 16383) 16383 + 16383 = 32766, dengan demikian tidak akan menghasilkan jumlah negatif.
sumber
rand()
tidak akan menghasilkan nol, jadi keinginan untuk menghindari nol bukanlah alasan yang baik untuk menambahkan dua nilai. Di sisi lain, keinginan untuk memiliki distribusi yang tidak seragam akan menjadi alasan yang baik untuk menambahkan dua nilai acak jika satu memastikan bahwa tidak terjadi overflow.Solusi sederhana (oke, sebut saja "Retas") yang tidak pernah menghasilkan hasil nol dan tidak akan pernah meluap adalah:
Ini akan membatasi nilai maksimum Anda, tetapi jika Anda tidak peduli tentang itu, maka ini akan berfungsi dengan baik untuk Anda.
sumber
rand()
selalu mengembalikan nilai tidak negatif). Namun, saya akan meninggalkan optimasi ke kompiler di sini.rand
akan menjadi non-negatif, perubahan akan lebih efisien daripada pembagian oleh bilangan bulat yang ditandatangani 2. Divisi dengan2u
dapat bekerja, tetapi jikax
suatuint
dapat menghasilkan peringatan tentang konversi implisit dari yang tidak ditandatangani. untuk ditandatangani./ 2
(saya telah melihat ini bahkan untuk sesuatu seperti-O0
, yaitu tanpa optimisasi diminta secara eksplisit). Ini mungkin optimasi kode C yang paling sepele dan paling mapan. Poinnya adalah pembagian didefinisikan dengan baik oleh standar untuk seluruh rentang bilangan bulat, bukan hanya nilai-nilai non-negatif. Sekali lagi: serahkan OPT ke compiler, tulis kode yang benar dan jelas di tempat pertama. Ini bahkan lebih penting bagi pemula.rand()
benar dengan satu atau membaginya dengan2u
saat membaginya dengan 2, bahkan ketika menggunakan-O3
. Orang mungkin mengatakan bahwa pengoptimalan semacam itu tidak akan menjadi masalah, tetapi mengatakan "serahkan pengoptimalan semacam itu ke kompiler" akan menyiratkan bahwa penyusun kemungkinan akan melakukan hal itu. Apakah Anda tahu ada kompiler yang benar-benar mau?Untuk menghindari 0, coba ini:
Anda harus memasukkan
limits.h
.sumber
rand()
menghasilkan 01
dan2
(semua diasumsikanRAND_MAX == INT_MAX
). Saya memang lupa tentang- 1
.-1
sini tidak ada nilainya.rand()%INT_MAX+1;
hanya akan menghasilkan nilai dalam kisaran [1 ... INT_MAX].Sementara apa yang orang lain katakan tentang kemungkinan overflow bisa jadi penyebab negatifnya, bahkan ketika Anda menggunakan bilangan bulat yang tidak ditandatangani. Masalah sebenarnya sebenarnya menggunakan fungsi waktu / tanggal sebagai unggulan. Jika Anda benar-benar menjadi terbiasa dengan fungsi ini, Anda akan tahu persis mengapa saya mengatakan ini. Seperti apa yang sebenarnya dilakukannya adalah memberi jarak (waktu berlalu) sejak tanggal / waktu tertentu. Meskipun penggunaan fungsi tanggal / waktu sebagai seed to rand (), adalah praktik yang sangat umum, itu sebenarnya bukan pilihan terbaik. Anda harus mencari alternatif yang lebih baik, karena ada banyak teori tentang topik ini dan saya tidak mungkin membahas semuanya. Anda menambahkan ke dalam persamaan ini kemungkinan overflow dan pendekatan ini sudah ditakdirkan sejak awal.
Mereka yang memposting rand () +1 menggunakan solusi yang paling banyak digunakan untuk menjamin bahwa mereka tidak mendapatkan angka negatif. Tapi, pendekatan itu juga bukan cara terbaik.
Hal terbaik yang dapat Anda lakukan adalah meluangkan waktu ekstra untuk menulis dan menggunakan penanganan pengecualian yang tepat, dan hanya menambah angka rand () jika dan / atau ketika Anda berakhir dengan hasil nol. Dan, untuk menangani angka negatif dengan benar. Fungsionalitas rand () tidak sempurna, dan karena itu perlu digunakan bersamaan dengan penanganan pengecualian untuk memastikan bahwa Anda mendapatkan hasil yang diinginkan.
Meluangkan waktu dan upaya ekstra untuk menyelidiki, mempelajari, dan mengimplementasikan fungsionalitas rand () dengan baik sepadan dengan waktu dan upaya. Hanya dua sen saya. Semoga beruntung dengan usahamu...
sumber
rand()
tidak menentukan benih apa yang akan digunakan. Standar tidak menetapkannya untuk menggunakan generator pseudorandom, bukan hubungan dengan waktu kapan saja. Itu juga tidak menyatakan tentang kualitas generator. Masalah yang sebenarnya jelas meluap. Catatan yangrand()+1
digunakan untuk menghindari0
;rand()
tidak mengembalikan nilai negatif. Maaf, tapi Anda tidak mengerti intinya di sini. Ini bukan tentang kualitas PRNG. .../dev/random
dan menggunakan PRNG yang baik sesudahnya (tidak yakin tentang kualitasrand()
dari glibc) atau terus menggunakan perangkat - mempertaruhkan aplikasi Anda untuk diblokir jika tidak tersedia cukup entropi. Mencoba memasukkan entropi Anda ke dalam aplikasi mungkin sangat rentan karena itu mungkin lebih mudah diserang. Dan sekarang sampai pada pengerasan - tidak di sini