Bu rasgele değerin neden 50/50 yerine 25/75 dağılımı var?


139

Düzenleme: Yani temelde ne yazmaya çalışıyorum için 1 bitlik bir karma olduğunu double.

Bir eşlemek istediğiniz doublekadar trueveya false50/50 şansı. Bunun için bazı rasgele sayılar alır kod yazdım (ben 50/50 sonuç almak yine normal tabloya veriler üzerinde kullanmak istediğiniz sadece bir örnek olarak) , çekler, son bit ve artışlarla yo 1, ya da eğer no takdirde 0.

Ancak, bu kod sürekli olarak% 25 yve% 75 ile sonuçlanır n. Neden 50/50 değil? Ve neden bu kadar garip ama düz (1/3) dağıtım?

public class DoubleToBoolean {
    @Test
    public void test() {

        int y = 0;
        int n = 0;
        Random r = new Random();
        for (int i = 0; i < 1000000; i++) {
            double randomValue = r.nextDouble();
            long lastBit = Double.doubleToLongBits(randomValue) & 1;
            if (lastBit == 1) {
                y++;
            } else {
                n++;
            }
        }
        System.out.println(y + " " + n);
    }
}

Örnek çıktı:

250167 749833

43
Cevabın, "LCG'nin düşük bitlerde düşük entropiye sahip olması" yerine, kayan nokta değişkenlerinin rastgele üretimi hakkında büyüleyici bir şey olduğunu umuyorum.
Sneftel

4
Çok merak ediyorum, "çift için 1 bit karma" nın amacı nedir? Böyle bir gereksinimin meşru bir şekilde uygulanmasını ciddi olarak düşünemiyorum.
corsiKa

3
@corsiKa Geometri hesaplamalarında genellikle iki olası cevap arasından seçim yapmak istediğimiz iki durum vardır (örn. çizginin soluna veya sağına mı işaret eder?) ve bazen üçüncü dejenere durumu (nokta ancak iki kullanılabilir cevabınız vardır, bu nedenle bu durumda mevcut yanıtlardan birini rastgele seçmeniz gerekir. Düşünebildiğim en iyi yol, verilen çift değerlerden birinin 1 bitlik karmasını almaktır (unutmayın, bunlar geometri hesaplamalarıdır, bu yüzden her yerde iki kat vardır).
gvlasov

2
@corsiKa (yorum çok uzun olduğu için ikiye ayrılmıştır) Daha basit bir şeye başlayabiliriz doubleValue % 1 > 0.5, ancak bazı durumlarda görünür düzenler getirebileceğinden çok kaba taneli olur (tüm değerler uzunluk 1 aralığındadır). Eğer bu çok iri taneli ise, muhtemelen daha küçük aralıkları denememiz gerekir doubleValue % 1e-10 > 0.5e-10mi? İyi evet. Ve sadece son biti bir hash olarak almak double, bu yaklaşımı sonuna kadar takip ettiğinizde mümkün olan en az modülo ile olan şeydir.
gvlasov

1
@kmote o zaman hala ağır önyargılı en az önemli bite sahip olacaksınız ve diğer bit de bunu telafi etmiyor - aslında aynı nedenden ötürü sıfıra (ama daha az) karşı da önyargılı. Yani dağılım yaklaşık 50, 12.5, 25, 12.5 olacaktır. (lastbit & 3) == 0olsa, garip olduğu gibi çalışır.
Harold

Yanıtlar:


165

Çünkü nextDouble şu şekilde çalışır: ( kaynak )

public double nextDouble()
{
    return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
}

next(x)xrastgele bitler yapar .

Şimdi bu neden önemli? Çünkü birinci bölüm (bölümden önce) tarafından üretilen sayıların yaklaşık yarısı daha küçüktür 1L << 52ve bu nedenle bunların önemi, doldurabileceği 53 biti tamamen doldurmaz, yani anlamlılığın en az önemli biti her zaman sıfırdır.


Bu dikkatin yoğunluğu nedeniyle, doubleJava'daki a'nın (ve diğer birçok dilin) ​​gerçekten nasıl göründüğüne ve bu soruda neden önemli olduğuna dair bazı ekstra açıklamalar .

Temel olarak, doubleşuna benzer: ( kaynak )

çift ​​düzen

Bu resimde görülmeyen çok önemli bir detay, sayıların "normalize edilmiş" 1 olması, böylece 53 bit kesiti 1 ile başlayacaktır (üslüyü öyle olacak şekilde seçerek), o zaman 1 atlanır. Bu yüzden resim kesir için 52 bit gösteriyor (anlamlı), ancak etkili bir şekilde 53 bit var.

Normalleştirme nextDouble, 53'üncü bit kodunda ayarlanırsa, bu bitin üstü kapalı 1 olduğu ve kaybolduğu ve diğer 52 bitin kelimenin tam anlamıyla elde edilen sonucun önemine kopyalandığı anlamına gelir double. Ancak bu bit ayarlanmazsa, kalan bitler ayarlanana kadar sola kaydırılmalıdır.

Ortalama olarak, üretilen sayıların yarısı, anlamlılığın hiç sola kaymadığı (ve bunların en az anlamlı biti olarak 0'a sahip olanların yarısı) ve diğer yarısının en az 1 kaydırıldığı (veya sadece tamamen sıfır) böylece en küçük anlamlı bitleri her zaman 0 olur.

1: her zaman değil, açıkça Bu numaralar denormal veya normalin altında sayılar denir hiçbir yüksek 1. olan, sıfır için yapılamaz, bkz denormal numarasını: wikipedia .


16
Yaşasın! Tam da umduğum gibi.
Sneftel

3
@Matt Muhtemelen bir hız optimizasyonu. Alternatif, üssü geometrik bir dağılımla ve daha sonra mantisi ayrı ayrı üretmek olacaktır.
Sneftel

7
@Matt: "En iyi" yi tanımlayın. random.nextDouble()genellikle amaçlanan şey için "en iyi" yoldur, ancak çoğu insan rastgele çiftliğinden 1 bitlik karma üretmeye çalışmaz. Eşit dağılım, kriptanaliz direnci veya neyi mi arıyorsunuz?
StriplingWarrior

1
Bu yanıt, OP'nin rasgele sayıyı 2 ^ 53 ile çarpması ve sonuçta elde edilen tamsayının garip olup olmadığını kontrol etmesinin 50/50 dağılım olacağını gösterir.
rici

4
The111 @ diyor burada o nextbir geri dönmelidir intsadece 32 bit kadar olabilir bu yüzden zaten
Harold

48

Gönderen docs :

NextDouble yöntemi, Rastgele sınıfı tarafından şu şekilde uygulanır:

public double nextDouble() {
  return (((long)next(26) << 27) + next(27))
      / (double)(1L << 53);
}

Ama aynı zamanda aşağıdakileri de belirtir (benimkini vurgular):

[Java'nın ilk sürümlerinde sonuç yanlış olarak hesaplandı:

 return (((long)next(27) << 27) + next(27))
     / (double)(1L << 54);

Bu, daha iyi olmasa da eşdeğer görünebilir, ancak aslında kayan nokta sayılarının yuvarlanmasındaki önyargı nedeniyle büyük bir tekdüzelik getirdi: anlamlılığın düşük dereceli bitinin 0 olması üç kat daha muhtemeldi daha 1 olurdu ! Bu tekdüzelik muhtemelen pratikte çok önemli değil, ama mükemmellik için çalışıyoruz.]

Bu not en azından Java 5'ten beri var (Java için dokümanlar <= 1.4 bir giriş duvarının arkasında, kontrol etmek için çok tembel). Bu ilginç, çünkü problem hala Java 8'de bile var. Belki de "sabit" versiyon hiç test edilmedi?


4
Garip. Bunu Java 8'de yeniden
ürettim

1
Bu ilginç, çünkü önyargının hala yeni yönteme uygulandığını iddia ettim. Yanlış mıyım?
harold

3
@harold: Hayır, bence haklısın ve bu önyargıyı düzeltmeye çalışan bir hata yapmış olabilir.
Thomas

6
@harold Java adamlarına e-posta gönderme zamanı.
Daniel

8
"Belki de sabit sürüm hiç test edilmedi?" Aslında, bunu yeniden okurken, doktorun farklı bir sorun hakkında olduğunu düşünüyorum. Yuvarlamadan bahsettiğine dikkat edin , bu da "olası üç katın" doğrudan sorun olduğunu düşünmediklerini, bunun yerine değerler yuvarlandığında eşit olmayan bir dağılıma yol açtığını düşündürmektedir . Cevabımda, listelediğim değerlerin eşit olarak dağıtıldığını, ancak IEEE biçiminde gösterilen düşük dereceli bitin aynı olmadığını unutmayın. Düzelttikleri sorunun, düşük bitin tekdüzeliği değil, genel tekdüzelik ile ilgisi olduğunu düşünüyorum.
ajb

33

Kayan nokta sayılarının nasıl temsil edildiği göz önüne alındığında bu sonuç beni şaşırtmıyor. Sadece 4 bit hassasiyetle çok kısa bir kayan nokta türümüz olduğunu varsayalım. Eğer eşit olarak dağıtılmış 0 ile 1 arasında rastgele bir sayı üretecek olsaydık, 16 olası değer olurdu:

0.0000
0.0001
0.0010
0.0011
0.0100
...
0.1110
0.1111

Makinede böyle görünüyorlarsa, 50/50 dağıtım elde etmek için düşük dereceli biti test edebilirsiniz. Bununla birlikte, IEEE şamandıraları bir mantisin 2 katı bir güç olarak temsil edilir; şamandıradaki bir alan 2'nin gücüdür (artı sabit bir ofset). 2'nin gücü, "mantis" kısmı daima> = 1.0 ve <2.0 olacak şekilde seçilir. Bu, aslında, sayı dışındaki rakamların 0.0000şu şekilde temsil edileceği anlamına gelir :

0.0001 = 2^(-4) x 1.000
0.0010 = 2^(-3) x 1.000
0.0011 = 2^(-3) x 1.100
0.0100 = 2^(-2) x 1.000
... 
0.0111 = 2^(-2) x 1.110
0.1000 = 2^(-1) x 1.000
0.1001 = 2^(-1) x 1.001
...
0.1110 = 2^(-1) x 1.110
0.1111 = 2^(-1) x 1.111

( 1İkili noktadan önce ima edilen bir değerdir; 32 ve 64 bitlik kayan noktalar için, aslında bunu tutmak için hiçbir bit ayrılmamıştır 1.)

Ancak yukarıdakilere bakmak, gösterimi bitlere dönüştürürseniz ve düşük bite bakarsanız, zamanın% 75'ini elde edeceğinizi neden göstermelidir. Bunun nedeni, 0 (düşük) değerden düşük 0.1000olan olası değerlerin yarısıdır ve mantislerinin kaydırılmasıyla 0'ın düşük bitte görünmesine neden olur. Durum, mantisin 52 bit (ima edilen 1 dahil değil) olduğu gibi aynıdır double.

@Sneftel yorumunda önerildiği gibi (Aslında, biz olabilir üretme vasıtasının dağıtımında 16'dan fazla olası değerler şunlardır:

0.0001000 with probability 1/128
0.0001001 with probability 1/128
...
0.0001111 with probability 1/128
0.001000  with probability 1/64
0.001001  with probability 1/64
...
0.01111   with probability 1/32 
0.1000    with probability 1/16
0.1001    with probability 1/16
...
0.1110    with probability 1/16
0.1111    with probability 1/16

Ama çoğu programcının bekleyeceği türden bir dağıtım olduğundan emin değilim, bu yüzden muhtemelen işe yaramaz. Ayrıca, rasgele kayan nokta değerleri genellikle olduğu gibi, değerler tamsayılar oluşturmak için kullanıldığında çok fazla kazanmaz.)


5
Rastgele bitler / baytlar / herhangi bir şey elde etmek için kayan nokta kullanmak beni ürpertir. 0 ve n arasındaki rastgele dağılımlar için bile, rastgele * n… 'den daha iyi alternatiflerimiz var (arc4random_uniform'a bakın)
mirabilos
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.