Rand () neden sayıları Linux'ta Mac'ten çok daha sık tekrarlıyor?


87

Üzerinde çalıştığım bir projenin parçası olarak C'de bir hashmap uyguluyordum ve rand()Linux'ta sayıları Mac'ten çok daha sık tekrarladığını fark ettiğimde test etmek için rastgele ekler kullanıyordum . RAND_MAXher iki platformda da 2147483647 / 0x7FFFFFFF'dir. Ben bir bayt dizi yapar RAND_MAX+1-long, RAND_MAXrastgele sayılar üretir , her bir yinelenen olup olmadığını notları ve görüldüğü gibi listeden denetler bu test programına düşürdüm .

#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 sürekli olarak yaklaşık 790 milyon kopya üretir. Mac sürekli olarak sadece bir tane üretir, bu yüzden neredeyse tekrarlamadan üretebileceği her rastgele sayıdan geçer . Birisi bana bunun nasıl çalıştığını açıklayabilir mi? Man sayfalarından farklı bir şey söyleyemem, her birinin hangi RNG'yi kullandığını söyleyemem ve çevrimiçi bir şey bulamıyorum. Teşekkürler!


4
Rand () 0..RAND_MAX dahil değerleri döndürdüğünden, dizinizin RAND_MAX + 1
Blastfurnace

21
RAND_MAX / e ~ = 790 milyon olduğunu fark etmiş olabilirsiniz. Ayrıca n sonsuzluğa yaklaştıkça (1-1 / n) ^ n sınırı 1 / e'dir.
David Schwartz

3
@DavidSchwartz Sizi doğru anlarsam, Linux'taki sayının neden sürekli olarak 790 milyon olduğunu açıklayabilir. Neden / nasıl Mac vermez: Ben o zaman soruyu tahmin değil birçok kez tekrar?
Theron S

26
Çalışma zamanı kitaplığında PRNG için kalite gereksinimi yoktur. Sadece gerçek gereksinim aynı tohumla tekrarlanabilirliktir. Görünüşe göre, linux'unuzdaki PRNG kalitesi Mac'inizden daha iyidir.
pmg

4
@chux Evet, ancak çarpmaya dayalı olduğu için durum asla sıfır olamaz veya sonuç (sonraki durum) da sıfır olur. Kaynak koduna dayanarak, sıfırla tohumlanırsa özel bir durum olarak sıfırı kontrol eder, ancak dizinin bir parçası olarak hiç sıfır üretmez.
Arkku

Yanıtlar:


119

İlk başta macOS gibi gelebilir rand() , herhangi bir sayıyı tekrarlamamak için bir şekilde daha iyi görünse de, üretilen bu sayı sayısıyla çok sayıda yinelemenin beklendiğini (aslında 790 milyon veya (2 31 -1) ) / e ). Benzer şekilde, sırayla sayılar arasında yineleme yapmak da hiçbir kopya üretmeyecektir, ancak çok rastgele kabul edilmeyecektir. Linux rand()uygulaması bu testte gerçek bir rastgele kaynaktan ayırt edilemezken, macOS rand()değildir.

İlk bakışta şaşırtıcı görünen başka bir şey, macOS'un rand()kopyaları bu kadar iyi önlemek için nasıl başarabileceğidir. Kaynak koduna baktığımızda , uygulamayı aşağıdaki gibi buluyoruz:

/*
 * 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));

Bu gerçekten RAND_MAXde sekans tekrarlanmadan önce 1 ile dahil olmak üzere tam olarak bir kez tüm sayılarla sonuçlanır . Bir sonraki durum çarpmaya dayalı olduğundan, durum asla sıfır olamaz (veya gelecekteki tüm durumlar da sıfır olamaz). Böylece gördüğünüz tekrarlanan sayı ilk sayıdır ve sıfır asla döndürülmeyen sayıdır.

Apple, en azından macOS (veya OS X) var olduğu sürece belgelerinde ve örneklerinde daha iyi rasgele sayı üreteçlerinin kullanımını teşvik ediyor, bu nedenle kalitesi rand()muhtemelen önemli sayılmıyor ve bunlardan biri ile sıkışmış durumdalar. mevcut en basit yalancı jeneratörler. (Daha önce belirttiğiniz gibi, bunların yerine rand()kullanım önerisi de yorumlanmıştır arc4random().)

İlgili bir not, rastlantısallık için bu (ve diğer birçok) testlerde iyi sonuçlar üreten bulabildiğim en basit sahte sayı üreteci xorshift * :

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

Bu uygulama, testinizde neredeyse 790 milyon kopyayla sonuçlanır.


5
1980'lerde yayınlanan bir dergi makalesi , PRNG'ler için "doğum günü problemine" dayalı istatistiksel bir test önerdi.
pjs

14
"Apple, belgelerinde daha iyi rasgele sayı üreteçlerinin kullanımını teşvik ediyor" -> Tabii ki Apple arc4random()arkasındaki kodları kullanabilir rand()ve iyi bir rand()sonuç alabilir . Programcıları farklı kodlamaya yönlendirmek yerine, daha iyi kütüphane işlevleri oluşturun. "Sadece takıldılar" onların seçimi.
chux - Monica'yı geri yükle

23
mac'larda sabit bir ofsetin olmaması rand()o kadar kötü hale getirir ki pratik kullanım için kullanışlı değildir: rand ()% 7 neden daima 0 döndürür? , Rand ()% 14 yalnızca 6 veya 13 değerlerini üretir
phuclv

4
@PeterCordes: randAynı tohumla yeniden çalıştırılmasının aynı diziyi üretmesi konusunda böyle bir gereksinim var . OpenBSD'ler randbozuldu ve bu sözleşmeye uymuyor.
R .. GitHub BUZA YARDIMCI DURDUR

8
@ R.. GitHubSTOPHELPINGICE rand()Aynı tohumla kütüphanenin farklı versiyonları arasında aynı sırayı üreten bir C gereksinimi görüyor musunuz ? Böyle bir garanti kütüphane sürümleri arasında regresyon testi için yararlı olabilir, ancak bunun için C gereksinimi bulamıyorum.
chux - Monica'yı

34

MacOS, stdlib'de belgesiz bir rand () işlevi sağlar. Tohumdan ayrılırsanız, çıkardığı ilk değerler 16807, 282475249, 1622650073, 984943658 ve 1144108930'dur. Hızlı arama , bu dizinin aşağıdaki formülü çok temel bir LCG rasgele sayı üretecine karşılık geldiğini gösterir:

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

Bu RNG'nin durumu tamamen tek bir 32 bit tamsayının değeri ile tanımlandığından, süresi çok uzun değildir. Daha net olmak gerekirse, bu kendisi her 2 tekrar 31 1 ila 2 arasında her değeri çıkış olarak verilmesi, 2 yineleme - 31 - 2'ye .

Linux'un tüm sürümleri için standart bir rand () uygulaması olduğunu düşünmüyorum , ancak sıklıkla kullanılan glibc rand () işlevi var. Tek bir 32 bit durum değişkeni yerine, bu, 1000'den fazla bitlik bir havuz kullanır; bu, tüm amaç ve amaçlar için hiçbir zaman tam olarak tekrarlanan bir sıra üretmez. Yine, bu RNG'den ilk birkaç çıkışı ilk önce tohumlamadan yazdırarak hangi sürüme sahip olduğunuzu öğrenebilirsiniz. (Glibc rand () işlevi 1804289383, 846930886, 1681692777, 1714636915 ve 1957747793 sayılarını üretir.)

Linux'ta (ve MacOS'ta neredeyse hiç çarpışma) almamanızın nedeni, rand () in Linux sürümünün temelde daha rasgele olmasıdır.


5
bir dereceye giremeyen rand()ile biri gibi zorunluluk davranmasınasrand(1);
pmg

5
rand()İn macOS için kaynak kodu mevcuttur: opensource.apple.com/source/Libc/Libc-1353.11.2/stdlib/FreeBSD/… FWIW, aynı testi kaynaktan derlenmiş olarak çalıştırdım ve gerçekten sonuçlandı sadece bir kopya. Apple, arc4random()örneklerinde ve belgelerinde diğer rasgele sayı üreteçlerinin ( Swift'i devralmadan önce olduğu gibi) kullanımını teşvik ediyor , bu nedenle rand()platformlarındaki yerel uygulamalarda kullanımı muhtemelen çok iyi değil, bu neden daha iyi olmadığını açıklayabilir.
Arkku

Cevabım için teşekkürler, bu sorumu cevaplıyor. Ve (2 ^ 31) -2 periyodu, gözlemlediğim gibi neden sonunda tekrarlamaya başlayacağını açıklıyor. Siz (@ r3mainer) rand()belgesiz olduğunu söylediniz , ancak @Arkku görünür kaynağa bir bağlantı sağladı. İkiniz bu dosyayı neden sistemimde bulamadığımı ve neden yalnızca int rand(void) __swift_unavailable("Use arc4random instead.");Mac bilgisayarlarda gördüğümü stdlib.hbiliyor musunuz? Sanırım @Arkku ile bağlantılı kod sadece içine derlenmiş ... hangi kütüphane?
Theron S

1
@TheronS C kütüphanesinde derlenir, libc /usr/lib/libc.dylib,. =)
Arkku

5
Hangi sürümü rand(), belirli bir C programı kullanımları "derleyici" veya "işletim sistemi", daha ziyade C standart kütüphanesinde uygulanmasıyla belirlenmez (örneğin glibc, libc.dylib, msvcrt*.dll).
Peter O.

10

rand()C standardı tarafından tanımlanır ve C standardı hangi algoritmanın kullanılacağını belirtmez. Apple, GNU / Linux uygulamanız için daha düşük bir algoritma kullanıyor: Linux uygulaması, testinizdeki gerçek bir rastgele kaynaktan ayırt edilemezken, Apple uygulaması sadece sayıları karıştırıyor.

Herhangi bir kalitede rastgele sayılar istiyorsanız, döndürdüğü sayıların kalitesi hakkında en azından bazı garantiler veren daha iyi bir PRNG kullanın veya basitçe /dev/urandomveya benzerlerinden okuyun . Daha sonra size kriptografik kalite numaraları verir, ancak yavaştır. Kendi başına çok yavaş olsa bile, /dev/urandomdiğer bazı daha hızlı PRNG'ye mükemmel tohumlar sağlayabilir.


Yanıtınız için teşekkürler. Aslında iyi bir PRNG'ye ihtiyacım yok, sadece hashibimde gizlenen bazı tanımsız davranışlar olduğu konusunda endişeliydim, sonra bu olasılığı ortadan kaldırdığımda ve platformların hala farklı davrandığında meraklandım.
Theron S

btw burada kriptografik olarak güvenli rasgele sayı üretecinin bir örneği: github.com/divinity76/phpcpp/commit/… - ama C yerine C ++ ve STL uygulayıcılarının tüm ağır kaldırmaları yapmasına izin veriyorum ..
hanshenrik

3
@hanshenrik Bir kripto RNG genellikle aşırı derecede basittir ve basit bir karma tablo için çok yavaştır.
PM 2Ring

1
@ PM2Ring Kesinlikle. Bir karma tablo karma öncelikle hızlı olması gerekir, iyi değil. Ancak, sadece hızlı değil aynı zamanda iyi bir karma tablo algoritması geliştirmek istiyorsanız, kriptografik karma algoritmalarının bazı numaralarını bilmenin yararlı olduğuna inanıyorum. En hızlı karma algoritmalarını bilmeyen en göze çarpan hataların çoğundan kaçınmanıza yardımcı olacaktır. Yine de, burada belirli bir uygulama için reklam vermezdim.
cmaster - reinstate monica

@cmaster Yeterince doğru. Karıştırma fonksiyonları ve çığ etkisi gibi şeyler hakkında biraz bilgi sahibi olmak iyi bir fikirdir . Neyse ki, çok fazla hızdan (doğru uygulandığında) ödün vermeyen iyi özelliklere sahip kripto olmayan karma işlevleri vardır, örneğin xxhash, murmur3 veya siphash.
PM 2Ring

5

Genel olarak, rand / srand çifti, sonuçlarda yüksek dereceli bitlerden daha az rastgelelik gösteren düşük dereceli bitler nedeniyle uzun süredir kullanımdan kaldırılmıştır. Bu, sonuçlarınızla ilgili bir şey olabilir veya olmayabilir, ancak bazı rand / srand uygulamaları artık daha güncel olsa da, daha eski uygulamalar devam ediyor ve rastgele kullanmanın daha iyi olduğunu hatırlamak için hala iyi bir fırsat olduğunu düşünüyorum. ). Arch Linux kutumda, rand (3) için aşağıdaki not hala kılavuz sayfasında:

  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.)

Bunun hemen altında, man sayfası aslında şimdiye kadar gördüğünüz en basit LC RNG'lerle ilgili ve küçük bir RAND_MAX'a sahip olan çok kısa, çok basit örnek uygulamalar veriyor. C standart kütüphanede bulunanlarla eşleşmediklerini sanmıyorum. Ya da en azından ummuyorum.

Genel olarak, standart kütüphaneden bir şey kullanacaksanız, mümkünse rastgele kullanın (man sayfası onu POSIX standardı olarak POSIX.1-2001'e göre listeler, ancak rand, C standartlaştırılmadan önce standart bir yöntemdir) . Ya da daha iyisi, açık Sayısal Tarifler (veya çevrimiçi arayın) veya Knuth çatlamak ve birini uygulamak. Gerçekten kolaydır ve en sık ihtiyaç duyduğunuz ve bilinen kalitede özelliklere sahip genel amaçlı bir RNG'ye sahip olmak için bunu sadece bir kez yapmanız yeterlidir.


Bağlam için teşekkürler. Aslında yüksek kaliteli rasgeleliğe ihtiyacım yok ve Rust'da olsa MT19937'yi uyguladım. Çoğunlukla iki platformun neden farklı davrandığını nasıl bulacağımızı merak ediyordum.
Theron S

1
Bazen en iyi sorular, katı ihtiyaç yerine basit bir ilgi ile sorulur - bunlar genellikle belirli bir merak noktasından iyi cevaplar paketi alan sorulardır. Seninki de onlardan biri. İşte tüm meraklı insanlara, gerçek ve orijinal hackerlara.
Thomas Kammeyer

Tavsiye rand () daha iyi yapmak yerine "rand () kullanmayı durdurmak için" komikti. Standarttaki hiçbir şey bunun belirli bir jeneratör olması gerektiğini söylemiyor.
boru

2
@pipe rand()'Daha iyi' yapmak daha yavaş yapmak anlamına gelirse (muhtemelen - kriptografik olarak güvenli rastgele sayılar çok çaba gerektirir), o zaman marjinal olarak daha tahmin edilebilir olsa bile hızlı tutmak daha iyidir. Vakaya göre: başlatılması uzun süren bir üretim uygulamamız vardı, bu da başlatılması yeterli entropinin oluşmasını beklemek zorunda olan bir RNG'ye kadar izledik… Bu kadar güvenli olması gerekmediği ortaya çıktı, 'daha kötü' bir RNG büyük bir gelişmeydi.
gidds
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.